Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/net/quic/congestion_control/cube_root.cc b/net/quic/congestion_control/cube_root.cc
new file mode 100644
index 0000000..c563ad9
--- /dev/null
+++ b/net/quic/congestion_control/cube_root.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/cube_root.h"
+
+#include "base/logging.h"
+
+namespace {
+
+// Find last bit in a 64-bit word.
+int FindMostSignificantBit(uint64 x) {
+  if (!x) {
+    return 0;
+  }
+  int r = 0;
+  if (x & 0xffffffff00000000ull) {
+    x >>= 32;
+    r += 32;
+  }
+  if (x & 0xffff0000u) {
+    x >>= 16;
+    r += 16;
+  }
+  if (x & 0xff00u) {
+    x >>= 8;
+    r += 8;
+  }
+  if (x & 0xf0u) {
+    x >>= 4;
+    r += 4;
+  }
+  if (x & 0xcu) {
+    x >>= 2;
+    r += 2;
+  }
+  if (x & 0x02u) {
+    x >>= 1;
+    r++;
+  }
+  if (x & 0x01u) {
+    r++;
+  }
+  return r;
+}
+
+// 6 bits table [0..63]
+const uint32 cube_root_table[] = {
+    0,  54,  54,  54, 118, 118, 118, 118, 123, 129, 134, 138, 143, 147, 151,
+  156, 157, 161, 164, 168, 170, 173, 176, 179, 181, 185, 187, 190, 192, 194,
+  197, 199, 200, 202, 204, 206, 209, 211, 213, 215, 217, 219, 221, 222, 224,
+  225, 227, 229, 231, 232, 234, 236, 237, 239, 240, 242, 244, 245, 246, 248,
+  250, 251, 252, 254
+};
+}  // namespace
+
+namespace net {
+
+// Calculate the cube root using a table lookup followed by one Newton-Raphson
+// iteration.
+uint32 CubeRoot::Root(uint64 a) {
+  uint32 msb = FindMostSignificantBit(a);
+  DCHECK_LE(msb, 64u);
+
+  if (msb < 7) {
+    // MSB in our table.
+    return ((cube_root_table[static_cast<uint32>(a)]) + 31) >> 6;
+  }
+  // MSB          7,  8,  9, 10, 11, 12, 13, 14, 15, 16, ...
+  // cubic_shift  1,  1,  1,  2,  2,  2,  3,  3,  3,  4, ...
+  uint32 cubic_shift = (msb - 4);
+  cubic_shift = ((cubic_shift * 342) >> 10);  // Div by 3, biased high.
+
+  // 4 to 6 bits accuracy depending on MSB.
+  uint32 down_shifted_to_6bit = (a >> (cubic_shift * 3));
+  uint64 root = ((cube_root_table[down_shifted_to_6bit] + 10) << cubic_shift)
+      >> 6;
+
+  // Make one Newton-Raphson iteration.
+  // Since x has an error (inaccuracy due to the use of fix point) we get a
+  // more accurate result by doing x * (x - 1) instead of x * x.
+  root = 2 * root + (a / (root * (root - 1)));
+  root = ((root * 341) >> 10);  // Div by 3, biased low.
+  return static_cast<uint32>(root);
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/cube_root.h b/net/quic/congestion_control/cube_root.h
new file mode 100644
index 0000000..293a719
--- /dev/null
+++ b/net/quic/congestion_control/cube_root.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
+#define NET_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE CubeRoot {
+ public:
+  // Calculates the cube root using a table lookup followed by one Newton-
+  // Raphson iteration.
+  static uint32 Root(uint64 a);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CubeRoot);
+};
+
+}  // namespace net
+#endif  // NET_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
diff --git a/net/quic/congestion_control/cube_root_test.cc b/net/quic/congestion_control/cube_root_test.cc
new file mode 100644
index 0000000..8f4729c
--- /dev/null
+++ b/net/quic/congestion_control/cube_root_test.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/cube_root.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class CubeRootTest : public ::testing::Test {
+ protected:
+  CubeRootTest() {
+  }
+};
+
+TEST_F(CubeRootTest, LowRoot) {
+  for (uint32 i = 1; i < 256; ++i) {
+    uint64 cube = i * i * i;
+    uint8 cube_root = CubeRoot::Root(cube);
+    EXPECT_EQ(i, cube_root);
+  }
+}
+
+TEST_F(CubeRootTest, HighRoot) {
+  // Test the range we will opperate in, 1300 to 130 000.
+  // We expect some loss in accuracy, accepting +-0.2%.
+  for (uint64 i = 1300; i < 20000; i += 100) {
+    uint64 cube = i * i * i;
+    uint32 cube_root = CubeRoot::Root(cube);
+    uint32 margin = cube_root >> 9;  // Calculate 0.2% roughly by
+                                     // dividing by 512.
+    EXPECT_LE(i - margin, cube_root);
+    EXPECT_GE(i + margin, cube_root);
+  }
+  for (uint64 i = 20000; i < 130000; i *= 2) {
+    uint64 cube = i * i * i;
+    uint32 cube_root = CubeRoot::Root(cube);
+    uint32 margin = cube_root >> 9;
+    EXPECT_LE(i - margin, cube_root);
+    EXPECT_GE(i + margin, cube_root);
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/cubic.cc b/net/quic/congestion_control/cubic.cc
new file mode 100644
index 0000000..dec529d
--- /dev/null
+++ b/net/quic/congestion_control/cubic.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/cubic.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "net/quic/congestion_control/cube_root.h"
+#include "net/quic/quic_protocol.h"
+
+using std::max;
+
+namespace net {
+
+namespace {
+
+// Constants based on TCP defaults.
+// The following constants are in 2^10 fractions of a second instead of ms to
+// allow a 10 shift right to divide.
+const int kCubeScale = 40;  // 1024*1024^3 (first 1024 is from 0.100^3)
+                            // where 0.100 is 100 ms which is the scaling
+                            // round trip time.
+const int kCubeCongestionWindowScale = 410;
+const uint64 kCubeFactor = (GG_UINT64_C(1) << kCubeScale) /
+    kCubeCongestionWindowScale;
+
+const uint32 kDefaultNumConnections = 2;
+const float kBeta = 0.7f;  // Default Cubic backoff factor.
+// Additional backoff factor when loss occurs in the concave part of the Cubic
+// curve. This additional backoff factor is expected to give up bandwidth to
+// new concurrent flows and speed up convergence.
+const float kBetaLastMax = 0.85f;
+
+}  // namespace
+
+Cubic::Cubic(const QuicClock* clock, QuicConnectionStats* stats)
+    : clock_(clock),
+      num_connections_(kDefaultNumConnections),
+      epoch_(QuicTime::Zero()),
+      last_update_time_(QuicTime::Zero()),
+      stats_(stats) {
+  Reset();
+}
+
+void Cubic::SetNumConnections(int num_connections) {
+  num_connections_ = num_connections;
+}
+
+float Cubic::Alpha() const {
+  // TCPFriendly alpha is described in Section 3.3 of the CUBIC paper. Note that
+  // beta here is a cwnd multiplier, and is equal to 1-beta from the paper.
+  // We derive the equivalent alpha for an N-connection emulation as:
+  const float beta = Beta();
+  return 3 * num_connections_ * num_connections_ * (1 - beta) / (1 + beta);
+}
+
+float Cubic::Beta() const {
+  // kNConnectionBeta is the backoff factor after loss for our N-connection
+  // emulation, which emulates the effective backoff of an ensemble of N
+  // TCP-Reno connections on a single loss event. The effective multiplier is
+  // computed as:
+  return (num_connections_ - 1 + kBeta) / num_connections_;
+}
+
+void Cubic::Reset() {
+  epoch_ = QuicTime::Zero();  // Reset time.
+  last_update_time_ = QuicTime::Zero();  // Reset time.
+  last_congestion_window_ = 0;
+  last_max_congestion_window_ = 0;
+  acked_packets_count_ = 0;
+  estimated_tcp_congestion_window_ = 0;
+  origin_point_congestion_window_ = 0;
+  time_to_origin_point_ = 0;
+  last_target_congestion_window_ = 0;
+}
+
+void Cubic::UpdateCongestionControlStats(
+    QuicTcpCongestionWindow new_cubic_mode_cwnd,
+    QuicTcpCongestionWindow new_reno_mode_cwnd) {
+
+  QuicTcpCongestionWindow highest_new_cwnd = std::max(new_cubic_mode_cwnd,
+                                                      new_reno_mode_cwnd);
+  if (last_congestion_window_ < highest_new_cwnd) {
+    // cwnd will increase to highest_new_cwnd.
+    stats_->cwnd_increase_congestion_avoidance +=
+        highest_new_cwnd - last_congestion_window_;
+    if (new_cubic_mode_cwnd > new_reno_mode_cwnd) {
+      // This cwnd increase is due to cubic mode.
+      stats_->cwnd_increase_cubic_mode +=
+          new_cubic_mode_cwnd - last_congestion_window_;
+    }
+  }
+}
+
+QuicTcpCongestionWindow Cubic::CongestionWindowAfterPacketLoss(
+    QuicTcpCongestionWindow current_congestion_window) {
+  if (current_congestion_window < last_max_congestion_window_) {
+    // We never reached the old max, so assume we are competing with another
+    // flow. Use our extra back off factor to allow the other flow to go up.
+    last_max_congestion_window_ =
+        static_cast<int>(kBetaLastMax * current_congestion_window);
+  } else {
+    last_max_congestion_window_ = current_congestion_window;
+  }
+  epoch_ = QuicTime::Zero();  // Reset time.
+  return static_cast<int>(current_congestion_window * Beta());
+}
+
+QuicTcpCongestionWindow Cubic::CongestionWindowAfterAck(
+    QuicTcpCongestionWindow current_congestion_window,
+    QuicTime::Delta delay_min) {
+  acked_packets_count_ += 1;  // Packets acked.
+  QuicTime current_time = clock_->ApproximateNow();
+
+  // Cubic is "independent" of RTT, the update is limited by the time elapsed.
+  if (last_congestion_window_ == current_congestion_window &&
+      (current_time.Subtract(last_update_time_) <= MaxCubicTimeInterval())) {
+    return max(last_target_congestion_window_,
+               estimated_tcp_congestion_window_);
+  }
+  last_congestion_window_ = current_congestion_window;
+  last_update_time_ = current_time;
+
+  if (!epoch_.IsInitialized()) {
+    // First ACK after a loss event.
+    DVLOG(1) << "Start of epoch";
+    epoch_ = current_time;  // Start of epoch.
+    acked_packets_count_ = 1;  // Reset count.
+    // Reset estimated_tcp_congestion_window_ to be in sync with cubic.
+    estimated_tcp_congestion_window_ = current_congestion_window;
+    if (last_max_congestion_window_ <= current_congestion_window) {
+      time_to_origin_point_ = 0;
+      origin_point_congestion_window_ = current_congestion_window;
+    } else {
+      time_to_origin_point_ = CubeRoot::Root(kCubeFactor *
+          (last_max_congestion_window_ - current_congestion_window));
+      origin_point_congestion_window_ =
+          last_max_congestion_window_;
+    }
+  }
+  // Change the time unit from microseconds to 2^10 fractions per second. Take
+  // the round trip time in account. This is done to allow us to use shift as a
+  // divide operator.
+  int64 elapsed_time =
+      (current_time.Add(delay_min).Subtract(epoch_).ToMicroseconds() << 10) /
+      base::Time::kMicrosecondsPerSecond;
+
+  int64 offset = time_to_origin_point_ - elapsed_time;
+  QuicTcpCongestionWindow delta_congestion_window = (kCubeCongestionWindowScale
+      * offset * offset * offset) >> kCubeScale;
+
+  QuicTcpCongestionWindow target_congestion_window =
+      origin_point_congestion_window_ - delta_congestion_window;
+
+  DCHECK_LT(0u, estimated_tcp_congestion_window_);
+  // With dynamic beta/alpha based on number of active streams, it is possible
+  // for the required_ack_count to become much lower than acked_packets_count_
+  // suddenly, leading to more than one iteration through the following loop.
+  while (true) {
+    // Update estimated TCP congestion_window.
+    uint32 required_ack_count =
+        estimated_tcp_congestion_window_ / Alpha();
+    if (acked_packets_count_ < required_ack_count) {
+      break;
+    }
+    acked_packets_count_ -= required_ack_count;
+    estimated_tcp_congestion_window_++;
+  }
+
+  // Update cubic mode and reno mode stats in QuicConnectionStats.
+  UpdateCongestionControlStats(target_congestion_window,
+                               estimated_tcp_congestion_window_);
+
+  // We have a new cubic congestion window.
+  last_target_congestion_window_ = target_congestion_window;
+
+  // Compute target congestion_window based on cubic target and estimated TCP
+  // congestion_window, use highest (fastest).
+  if (target_congestion_window < estimated_tcp_congestion_window_) {
+    target_congestion_window = estimated_tcp_congestion_window_;
+  }
+
+  DVLOG(1) << "Target congestion_window: " << target_congestion_window;
+  return target_congestion_window;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/cubic.h b/net/quic/congestion_control/cubic.h
new file mode 100644
index 0000000..d9f7bb6
--- /dev/null
+++ b/net/quic/congestion_control/cubic.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Cubic algorithm, helper class to TCP cubic.
+// For details see http://netsrv.csc.ncsu.edu/export/cubic_a_new_tcp_2008.pdf.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
+#define NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// TCP congestion window in QUIC is in packets, not bytes.
+typedef uint32 QuicTcpCongestionWindow;
+
+class NET_EXPORT_PRIVATE Cubic {
+ public:
+  Cubic(const QuicClock* clock, QuicConnectionStats* stats);
+
+  void SetNumConnections(int num_connections);
+
+  // Call after a timeout to reset the cubic state.
+  void Reset();
+
+  // Compute a new congestion window to use after a loss event.
+  // Returns the new congestion window in packets. The new congestion window is
+  // a multiplicative decrease of our current window.
+  QuicTcpCongestionWindow CongestionWindowAfterPacketLoss(
+      QuicTcpCongestionWindow current);
+
+  // Compute a new congestion window to use after a received ACK.
+  // Returns the new congestion window in packets. The new congestion window
+  // follows a cubic function that depends on the time passed since last
+  // packet loss.
+  QuicTcpCongestionWindow CongestionWindowAfterAck(
+      QuicTcpCongestionWindow current,
+      QuicTime::Delta delay_min);
+
+ private:
+  static const QuicTime::Delta MaxCubicTimeInterval() {
+    return QuicTime::Delta::FromMilliseconds(30);
+  }
+
+  // Compute the TCP Cubic alpha and beta based on the current number of
+  // connections.
+  float Alpha() const;
+  float Beta() const;
+
+  // Update congestion control variables in QuicConnectionStats.
+  void UpdateCongestionControlStats(QuicTcpCongestionWindow new_cubic_mode_cwnd,
+                                    QuicTcpCongestionWindow new_reno_mode_cwnd);
+  const QuicClock* clock_;
+
+  // Number of connections to simulate.
+  int num_connections_;
+
+  // Time when this cycle started, after last loss event.
+  QuicTime epoch_;
+
+  // Time when we updated last_congestion_window.
+  QuicTime last_update_time_;
+
+  // Last congestion window (in packets) used.
+  QuicTcpCongestionWindow last_congestion_window_;
+
+  // Max congestion window (in packets) used just before last loss event.
+  // Note: to improve fairness to other streams an additional back off is
+  // applied to this value if the new value is below our latest value.
+  QuicTcpCongestionWindow last_max_congestion_window_;
+
+  // Number of acked packets since the cycle started (epoch).
+  uint32 acked_packets_count_;
+
+  // TCP Reno equivalent congestion window in packets.
+  QuicTcpCongestionWindow estimated_tcp_congestion_window_;
+
+  // Origin point of cubic function.
+  QuicTcpCongestionWindow origin_point_congestion_window_;
+
+  // Time to origin point of cubic function in 2^10 fractions of a second.
+  uint32 time_to_origin_point_;
+
+  // Last congestion window in packets computed by cubic function.
+  QuicTcpCongestionWindow last_target_congestion_window_;
+
+  // QuicConnectionStats includes congestion control related stats.
+  QuicConnectionStats* stats_;
+
+  DISALLOW_COPY_AND_ASSIGN(Cubic);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
diff --git a/net/quic/congestion_control/cubic_test.cc b/net/quic/congestion_control/cubic_test.cc
new file mode 100644
index 0000000..915274a
--- /dev/null
+++ b/net/quic/congestion_control/cubic_test.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/quic/congestion_control/cubic.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+const float kBeta = 0.7f;  // Default Cubic backoff factor.
+const uint32 kNumConnections = 2;
+const float kNConnectionBeta = (kNumConnections - 1 + kBeta) / kNumConnections;
+const float kNConnectionAlpha = 3 * kNumConnections * kNumConnections *
+      (1 - kNConnectionBeta) / (1 + kNConnectionBeta);
+
+class CubicTest : public ::testing::Test {
+ protected:
+  CubicTest()
+      : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+        hundred_ms_(QuicTime::Delta::FromMilliseconds(100)),
+        cubic_(&clock_, &stats_) {
+  }
+  const QuicTime::Delta one_ms_;
+  const QuicTime::Delta hundred_ms_;
+  MockClock clock_;
+  QuicConnectionStats stats_;
+  Cubic cubic_;
+};
+
+TEST_F(CubicTest, AboveOrigin) {
+  // Convex growth.
+  const QuicTime::Delta rtt_min = hundred_ms_;
+  uint32 current_cwnd = 10;
+  uint32 expected_cwnd = current_cwnd + 1;
+  // Initialize the state.
+  clock_.AdvanceTime(one_ms_);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min));
+  current_cwnd = expected_cwnd;
+  // Normal TCP phase.
+  for (int i = 0; i < 48; ++i) {
+    for (uint32 n = 1; n < current_cwnd / kNConnectionAlpha; ++n) {
+      // Call once per ACK.
+      EXPECT_NEAR(current_cwnd,
+                  cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min), 1);
+    }
+    clock_.AdvanceTime(hundred_ms_);
+    current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+    EXPECT_NEAR(expected_cwnd, current_cwnd, 1);
+    expected_cwnd++;
+  }
+  // Cubic phase.
+  for (int i = 0; i < 52; ++i) {
+    for (uint32 n = 1; n < current_cwnd; ++n) {
+      // Call once per ACK.
+      EXPECT_EQ(current_cwnd,
+                cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min));
+    }
+    clock_.AdvanceTime(hundred_ms_);
+    current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+  }
+  // Total time elapsed so far; add min_rtt (0.1s) here as well.
+  float elapsed_time_s = 10.0f + 0.1f;
+  // |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4.
+  expected_cwnd = 11 + (elapsed_time_s * elapsed_time_s * elapsed_time_s * 410)
+      / 1024;
+  EXPECT_EQ(expected_cwnd, current_cwnd);
+}
+
+TEST_F(CubicTest, CwndIncreaseStatsDuringConvexRegion) {
+  const QuicTime::Delta rtt_min = hundred_ms_;
+  uint32 current_cwnd = 10;
+  uint32 expected_cwnd = current_cwnd + 1;
+  // Initialize controller state.
+  clock_.AdvanceTime(one_ms_);
+  expected_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+  current_cwnd = expected_cwnd;
+  // Testing Reno mode increase.
+  for (int i = 0; i < 48; ++i) {
+    for (uint32 n = 1; n < current_cwnd / kNConnectionAlpha; ++n) {
+      // Call once per ACK, causing cwnd growth in Reno mode.
+      cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+    }
+    // Advance current time so that cwnd update is allowed to happen by Cubic.
+    clock_.AdvanceTime(hundred_ms_);
+    current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+    EXPECT_NEAR(expected_cwnd - 10, stats_.cwnd_increase_congestion_avoidance,
+                1);
+    EXPECT_NEAR(1u, stats_.cwnd_increase_cubic_mode, 1);
+    expected_cwnd++;
+  }
+  uint32 old_cwnd = current_cwnd;
+  stats_.cwnd_increase_cubic_mode = 0;
+  stats_.cwnd_increase_congestion_avoidance = 0;
+
+  // Testing Cubic mode increase.
+  for (int i = 0; i < 52; ++i) {
+    for (uint32 n = 1; n < current_cwnd; ++n) {
+      // Call once per ACK.
+      cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+    }
+    clock_.AdvanceTime(hundred_ms_);
+    current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+  }
+  // Total time elapsed so far; add min_rtt (0.1s) here as well.
+  float elapsed_time_s = 10.0f + 0.1f;
+  // |expected_cwnd| is initial value of cwnd + K * t^3, where K = 0.4.
+  expected_cwnd = 11 + (elapsed_time_s * elapsed_time_s * elapsed_time_s * 410)
+      / 1024;
+  EXPECT_EQ(expected_cwnd - old_cwnd, stats_.cwnd_increase_cubic_mode);
+  EXPECT_EQ(expected_cwnd - old_cwnd,
+            stats_.cwnd_increase_congestion_avoidance);
+}
+
+
+TEST_F(CubicTest, LossEvents) {
+  const QuicTime::Delta rtt_min = hundred_ms_;
+  uint32 current_cwnd = 422;
+  uint32 expected_cwnd = current_cwnd + 1;
+  // Initialize the state.
+  clock_.AdvanceTime(one_ms_);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min));
+  expected_cwnd = static_cast<int>(current_cwnd * kNConnectionBeta);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterPacketLoss(current_cwnd));
+  expected_cwnd = static_cast<int>(current_cwnd * kNConnectionBeta);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterPacketLoss(current_cwnd));
+}
+
+TEST_F(CubicTest, BelowOrigin) {
+  // Concave growth.
+  const QuicTime::Delta rtt_min = hundred_ms_;
+  uint32 current_cwnd = 422;
+  uint32 expected_cwnd = current_cwnd + 1;
+  // Initialize the state.
+  clock_.AdvanceTime(one_ms_);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min));
+  expected_cwnd = static_cast<int>(current_cwnd * kNConnectionBeta);
+  EXPECT_EQ(expected_cwnd,
+            cubic_.CongestionWindowAfterPacketLoss(current_cwnd));
+  current_cwnd = expected_cwnd;
+  // First update after loss to initialize the epoch.
+  current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+  uint32 old_cwnd =  current_cwnd;
+  // Cubic phase.
+  stats_.cwnd_increase_cubic_mode = 0;
+  stats_.cwnd_increase_congestion_avoidance = 0;
+  for (int i = 0; i < 40 ; ++i) {
+    clock_.AdvanceTime(hundred_ms_);
+    current_cwnd = cubic_.CongestionWindowAfterAck(current_cwnd, rtt_min);
+  }
+  expected_cwnd = 422;
+  EXPECT_EQ(expected_cwnd, current_cwnd);
+  EXPECT_EQ(expected_cwnd - old_cwnd, stats_.cwnd_increase_cubic_mode);
+  EXPECT_EQ(expected_cwnd - old_cwnd,
+            stats_.cwnd_increase_congestion_avoidance);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/hybrid_slow_start.cc b/net/quic/congestion_control/hybrid_slow_start.cc
new file mode 100644
index 0000000..2ae3b50
--- /dev/null
+++ b/net/quic/congestion_control/hybrid_slow_start.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+
+#include <algorithm>
+
+using std::max;
+using std::min;
+
+namespace net {
+
+// Note(pwestin): the magic clamping numbers come from the original code in
+// tcp_cubic.c.
+const int64 kHybridStartLowWindow = 16;
+// Number of delay samples for detecting the increase of delay.
+const uint32 kHybridStartMinSamples = 8;
+const int kHybridStartDelayFactorExp = 4;  // 2^4 = 16
+// The original paper specifies 2 and 8ms, but those have changed over time.
+const int kHybridStartDelayMinThresholdUs = 4000;
+const int kHybridStartDelayMaxThresholdUs = 16000;
+
+HybridSlowStart::HybridSlowStart(const QuicClock* clock)
+    : clock_(clock),
+      started_(false),
+      hystart_found_(NOT_FOUND),
+      last_sent_sequence_number_(0),
+      round_start_(QuicTime::Zero()),
+      end_sequence_number_(0),
+      last_close_ack_pair_time_(QuicTime::Zero()),
+      rtt_sample_count_(0),
+      current_min_rtt_(QuicTime::Delta::Zero()) {
+}
+
+void HybridSlowStart::OnPacketAcked(
+    QuicPacketSequenceNumber acked_sequence_number, bool in_slow_start) {
+  // OnPacketAcked gets invoked after ShouldExitSlowStart, so it's best to end
+  // the round when the final packet of the burst is received and start it on
+  // the next incoming ack.
+  if (in_slow_start && IsEndOfRound(acked_sequence_number)) {
+    started_ = false;
+  }
+}
+
+void HybridSlowStart::OnPacketSent(QuicPacketSequenceNumber sequence_number) {
+  last_sent_sequence_number_ = sequence_number;
+}
+
+void HybridSlowStart::Restart() {
+  started_ = false;
+  hystart_found_ = NOT_FOUND;
+}
+
+void HybridSlowStart::StartReceiveRound(QuicPacketSequenceNumber last_sent) {
+  DVLOG(1) << "Reset hybrid slow start @" << last_sent;
+  round_start_ = last_close_ack_pair_time_ = clock_->ApproximateNow();
+  end_sequence_number_ = last_sent;
+  current_min_rtt_ = QuicTime::Delta::Zero();
+  rtt_sample_count_ = 0;
+  started_ = true;
+}
+
+bool HybridSlowStart::IsEndOfRound(QuicPacketSequenceNumber ack) const {
+  return end_sequence_number_ <= ack;
+}
+
+bool HybridSlowStart::ShouldExitSlowStart(QuicTime::Delta latest_rtt,
+                                          QuicTime::Delta min_rtt,
+                                          int64 congestion_window) {
+  if (!started_) {
+    // Time to start the hybrid slow start.
+    StartReceiveRound(last_sent_sequence_number_);
+  }
+  if (hystart_found_ != NOT_FOUND) {
+    return true;
+  }
+  QuicTime current_time = clock_->ApproximateNow();
+
+  // First detection parameter - ack-train detection.
+  // Since slow start burst out packets we can indirectly estimate the inter-
+  // arrival time by looking at the arrival time of the ACKs if the ACKs are
+  // spread out more then half the minimum RTT packets are being spread out
+  // more than the capacity.
+  // This first trigger will not come into play until we hit roughly 9.6 Mbps
+  // with delayed acks (or 4.8Mbps without delayed acks)
+  // TODO(ianswett): QUIC always uses delayed acks, even at the beginning, so
+  // this should likely be at least 4ms.
+  // TODO(pwestin): we need to make sure our pacing don't trigger this detector.
+  // TODO(ianswett): Pacing or other cases could be handled by checking the send
+  // time of the first acked packet in a receive round.
+  if (current_time.Subtract(last_close_ack_pair_time_).ToMicroseconds() <=
+          kHybridStartDelayMinThresholdUs) {
+    last_close_ack_pair_time_ = current_time;
+    if (current_time.Subtract(round_start_).ToMicroseconds() >=
+            min_rtt.ToMicroseconds() >> 1) {
+      hystart_found_ = ACK_TRAIN;
+    }
+  } else if (last_close_ack_pair_time_ == round_start_) {
+    // If the previous ack wasn't close, then move forward the round start time
+    // to the incoming ack.
+    last_close_ack_pair_time_ = round_start_ = current_time;
+  }
+  // Second detection parameter - delay increase detection.
+  // Compare the minimum delay (current_min_rtt_) of the current
+  // burst of packets relative to the minimum delay during the session.
+  // Note: we only look at the first few(8) packets in each burst, since we
+  // only want to compare the lowest RTT of the burst relative to previous
+  // bursts.
+  rtt_sample_count_++;
+  if (rtt_sample_count_ <= kHybridStartMinSamples) {
+    if (current_min_rtt_.IsZero() || current_min_rtt_ > latest_rtt) {
+      current_min_rtt_ = latest_rtt;
+    }
+  }
+  // We only need to check this once per round.
+  if (rtt_sample_count_ == kHybridStartMinSamples) {
+    // Divide min_rtt by 16 to get a rtt increase threshold for exiting.
+    int min_rtt_increase_threshold_us = min_rtt.ToMicroseconds() >>
+        kHybridStartDelayFactorExp;
+    // Ensure the rtt threshold is never less than 2ms or more than 16ms.
+    min_rtt_increase_threshold_us = min(min_rtt_increase_threshold_us,
+                                        kHybridStartDelayMaxThresholdUs);
+    QuicTime::Delta min_rtt_increase_threshold =
+        QuicTime::Delta::FromMicroseconds(max(min_rtt_increase_threshold_us,
+                                              kHybridStartDelayMinThresholdUs));
+
+    if (current_min_rtt_ > min_rtt.Add(min_rtt_increase_threshold)) {
+      hystart_found_= DELAY;
+    }
+  }
+  // Exit from slow start if the cwnd is greater than 16 and an ack train or
+  // increasing delay are found.
+  return congestion_window >= kHybridStartLowWindow &&
+      hystart_found_ != NOT_FOUND;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/hybrid_slow_start.h b/net/quic/congestion_control/hybrid_slow_start.h
new file mode 100644
index 0000000..5d36c53
--- /dev/null
+++ b/net/quic/congestion_control/hybrid_slow_start.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This class is a helper class to TcpCubicSender.
+// Slow start is the initial startup phase of TCP, it lasts until first packet
+// loss. This class implements hybrid slow start of the TCP cubic send side
+// congestion algorithm. The key feaure of hybrid slow start is that it tries to
+// avoid running into the wall too hard during the slow start phase, which
+// the traditional TCP implementation does.
+// http://netsrv.csc.ncsu.edu/export/hybridstart_pfldnet08.pdf
+// http://research.csc.ncsu.edu/netsrv/sites/default/files/hystart_techreport_2008.pdf
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
+#define NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE HybridSlowStart {
+ public:
+  explicit HybridSlowStart(const QuicClock* clock);
+
+  void OnPacketAcked(QuicPacketSequenceNumber acked_sequence_number,
+                     bool in_slow_start);
+
+  void OnPacketSent(QuicPacketSequenceNumber sequence_number);
+
+  // ShouldExitSlowStart should be called on every new ack frame, since a new
+  // RTT measurement can be made then.
+  // rtt: the RTT for this ack packet.
+  // min_rtt: is the lowest delay (RTT) we have seen during the session.
+  // congestion_window: the congestion window in packets.
+  bool ShouldExitSlowStart(QuicTime::Delta rtt,
+                           QuicTime::Delta min_rtt,
+                           int64 congestion_window);
+
+  // Start a new slow start phase.
+  void Restart();
+
+  // TODO(ianswett): The following methods should be private, but that requires
+  // a follow up CL to update the unit test.
+  // Returns true if this ack the last sequence number of our current slow start
+  // round.
+  // Call Reset if this returns true.
+  bool IsEndOfRound(QuicPacketSequenceNumber ack) const;
+
+  // Call for the start of each receive round (burst) in the slow start phase.
+  void StartReceiveRound(QuicPacketSequenceNumber last_sent);
+
+  // Whether slow start has started.
+  bool started() const {
+    return started_;
+  }
+
+ private:
+  // Whether a condition for exiting slow start has been found.
+  enum HystartState {
+    NOT_FOUND,
+    ACK_TRAIN,  // A closely spaced ack train is too long.
+    DELAY,  // Too much increase in the round's min_rtt was observed.
+  };
+
+  const QuicClock* clock_;
+  // Whether the hybrid slow start has been started.
+  bool started_;
+  HystartState hystart_found_;
+  // Last sequence number sent which was CWND limited.
+  QuicPacketSequenceNumber last_sent_sequence_number_;
+
+  // Variables for tracking acks received during a slow start round.
+  QuicTime round_start_;  // Beginning of each slow start receive round.
+  QuicPacketSequenceNumber end_sequence_number_;  // End of the receive round.
+  // Last time when the spacing between ack arrivals was less than 2 ms.
+  // Defaults to the beginning of the round.
+  QuicTime last_close_ack_pair_time_;
+  uint32 rtt_sample_count_;  // Number of rtt samples in the current round.
+  QuicTime::Delta current_min_rtt_;  // The minimum rtt of current round.
+
+  DISALLOW_COPY_AND_ASSIGN(HybridSlowStart);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
diff --git a/net/quic/congestion_control/hybrid_slow_start_test.cc b/net/quic/congestion_control/hybrid_slow_start_test.cc
new file mode 100644
index 0000000..bea5840
--- /dev/null
+++ b/net/quic/congestion_control/hybrid_slow_start_test.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class HybridSlowStartTest : public ::testing::Test {
+ protected:
+  HybridSlowStartTest()
+     : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+       rtt_(QuicTime::Delta::FromMilliseconds(60)) {
+  }
+  virtual void SetUp() {
+    slow_start_.reset(new HybridSlowStart(&clock_));
+  }
+  const QuicTime::Delta one_ms_;
+  const QuicTime::Delta rtt_;
+  MockClock clock_;
+  scoped_ptr<HybridSlowStart> slow_start_;
+};
+
+TEST_F(HybridSlowStartTest, Simple) {
+  QuicPacketSequenceNumber sequence_number = 1;
+  QuicPacketSequenceNumber end_sequence_number = 3;
+  slow_start_->StartReceiveRound(end_sequence_number);
+
+  EXPECT_FALSE(slow_start_->IsEndOfRound(sequence_number++));
+
+  // Test duplicates.
+  EXPECT_FALSE(slow_start_->IsEndOfRound(sequence_number));
+
+  EXPECT_FALSE(slow_start_->IsEndOfRound(sequence_number++));
+  EXPECT_TRUE(slow_start_->IsEndOfRound(sequence_number++));
+
+  // Test without a new registered end_sequence_number;
+  EXPECT_TRUE(slow_start_->IsEndOfRound(sequence_number++));
+
+  end_sequence_number = 20;
+  slow_start_->StartReceiveRound(end_sequence_number);
+  while (sequence_number < end_sequence_number) {
+    EXPECT_FALSE(slow_start_->IsEndOfRound(sequence_number++));
+  }
+  EXPECT_TRUE(slow_start_->IsEndOfRound(sequence_number++));
+}
+
+// TODO(ianswett): Add tests which more realistically invoke the methods,
+// simulating how actual acks arrive and packets are sent.
+TEST_F(HybridSlowStartTest, AckTrain) {
+  // At a typical RTT 60 ms, assuming that the inter arrival timestamp is 1 ms,
+  // we expect to be able to send a burst of 30 packet before we trigger the
+  // ack train detection.
+  const int kMaxLoopCount = 5;
+  QuicPacketSequenceNumber sequence_number = 2;
+  QuicPacketSequenceNumber end_sequence_number = 2;
+  for (int burst = 0; burst < kMaxLoopCount; ++burst) {
+    slow_start_->StartReceiveRound(end_sequence_number);
+    do {
+      clock_.AdvanceTime(one_ms_);
+      EXPECT_FALSE(slow_start_->ShouldExitSlowStart(rtt_, rtt_, 100));
+    }  while (!slow_start_->IsEndOfRound(sequence_number++));
+    end_sequence_number *= 2;  // Exponential growth.
+  }
+  slow_start_->StartReceiveRound(end_sequence_number);
+
+  for (int n = 0;
+       n < 29 && !slow_start_->IsEndOfRound(sequence_number++); ++n) {
+    clock_.AdvanceTime(one_ms_);
+    EXPECT_FALSE(slow_start_->ShouldExitSlowStart(rtt_, rtt_, 100));
+  }
+  clock_.AdvanceTime(one_ms_);
+  EXPECT_TRUE(slow_start_->ShouldExitSlowStart(rtt_, rtt_, 100));
+}
+
+TEST_F(HybridSlowStartTest, Delay) {
+  // We expect to detect the increase at +1/16 of the RTT; hence at a typical
+  // RTT of 60ms the detection will happen at 63.75 ms.
+  const int kHybridStartMinSamples = 8;  // Number of acks required to trigger.
+
+  QuicPacketSequenceNumber end_sequence_number = 1;
+  slow_start_->StartReceiveRound(end_sequence_number++);
+
+  // Will not trigger since our lowest RTT in our burst is the same as the long
+  // term RTT provided.
+  for (int n = 0; n < kHybridStartMinSamples; ++n) {
+    EXPECT_FALSE(slow_start_->ShouldExitSlowStart(
+        rtt_.Add(QuicTime::Delta::FromMilliseconds(n)), rtt_, 100));
+  }
+  slow_start_->StartReceiveRound(end_sequence_number++);
+  for (int n = 1; n < kHybridStartMinSamples; ++n) {
+    EXPECT_FALSE(slow_start_->ShouldExitSlowStart(
+        rtt_.Add(QuicTime::Delta::FromMilliseconds(n + 5)), rtt_, 100));
+  }
+  // Expect to trigger since all packets in this burst was above the long term
+  // RTT provided.
+  EXPECT_TRUE(slow_start_->ShouldExitSlowStart(
+      rtt_.Add(QuicTime::Delta::FromMilliseconds(5)), rtt_, 100));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/leaky_bucket.cc b/net/quic/congestion_control/leaky_bucket.cc
new file mode 100644
index 0000000..f3972f6
--- /dev/null
+++ b/net/quic/congestion_control/leaky_bucket.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/leaky_bucket.h"
+
+#include "base/time/time.h"
+
+namespace net {
+
+LeakyBucket::LeakyBucket(QuicBandwidth draining_rate)
+    : bytes_(0),
+      time_last_updated_(QuicTime::Zero()),
+      draining_rate_(draining_rate) {
+}
+
+void LeakyBucket::SetDrainingRate(QuicTime now, QuicBandwidth draining_rate) {
+  Update(now);
+  draining_rate_ = draining_rate;
+}
+
+void LeakyBucket::Add(QuicTime now, QuicByteCount bytes) {
+  Update(now);
+  bytes_ += bytes;
+}
+
+QuicTime::Delta LeakyBucket::TimeRemaining(QuicTime now) const {
+  QuicTime::Delta time_since_last_update = now.Subtract(time_last_updated_);
+  QuicTime::Delta send_delay = QuicTime::Delta::FromMicroseconds(
+      (bytes_ * base::Time::kMicrosecondsPerSecond) /
+      draining_rate_.ToBytesPerSecond());
+  if (send_delay < time_since_last_update) {
+    return QuicTime::Delta::Zero();
+  }
+  return send_delay.Subtract(time_since_last_update);
+}
+
+QuicByteCount LeakyBucket::BytesPending(QuicTime now) {
+  Update(now);
+  return bytes_;
+}
+
+void LeakyBucket::Update(QuicTime now) {
+  QuicTime::Delta elapsed_time = now.Subtract(time_last_updated_);
+  QuicByteCount bytes_cleared = draining_rate_.ToBytesPerPeriod(elapsed_time);
+  if (bytes_cleared >= bytes_) {
+    bytes_ = 0;
+  } else {
+    bytes_ -= bytes_cleared;
+  }
+  time_last_updated_ = now;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/leaky_bucket.h b/net/quic/congestion_control/leaky_bucket.h
new file mode 100644
index 0000000..eb4cdb0
--- /dev/null
+++ b/net/quic/congestion_control/leaky_bucket.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Helper class to track the rate data can leave the buffer for pacing.
+// A leaky bucket drains the data at a constant rate regardless of fullness of
+// the buffer.
+// See http://en.wikipedia.org/wiki/Leaky_bucket for more details.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
+#define NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE LeakyBucket {
+ public:
+  explicit LeakyBucket(QuicBandwidth draining_rate);
+
+  // Set the rate at which the bytes leave the buffer.
+  void SetDrainingRate(QuicTime now, QuicBandwidth draining_rate);
+
+  // Add data to the buffer.
+  void Add(QuicTime now, QuicByteCount bytes);
+
+  // Time until the buffer is empty.
+  QuicTime::Delta TimeRemaining(QuicTime now) const;
+
+  // Number of bytes in the buffer.
+  QuicByteCount BytesPending(QuicTime now);
+
+ private:
+  void Update(QuicTime now);
+
+  QuicByteCount bytes_;
+  QuicTime time_last_updated_;
+  QuicBandwidth draining_rate_;
+
+  DISALLOW_COPY_AND_ASSIGN(LeakyBucket);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
diff --git a/net/quic/congestion_control/leaky_bucket_test.cc b/net/quic/congestion_control/leaky_bucket_test.cc
new file mode 100644
index 0000000..977ef94
--- /dev/null
+++ b/net/quic/congestion_control/leaky_bucket_test.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/leaky_bucket.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class LeakyBucketTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    leaky_bucket_.reset(new LeakyBucket(QuicBandwidth::Zero()));
+  }
+  MockClock clock_;
+  scoped_ptr<LeakyBucket> leaky_bucket_;
+};
+
+TEST_F(LeakyBucketTest, Basic) {
+  QuicBandwidth draining_rate = QuicBandwidth::FromBytesPerSecond(200000);
+  leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+  leaky_bucket_->Add(clock_.Now(), 2000);
+  EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(5),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+  leaky_bucket_->Add(clock_.Now(), 2000);
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(11));
+  EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+  leaky_bucket_->Add(clock_.Now(), 2000);
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  leaky_bucket_->Add(clock_.Now(), 2000);
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+  EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+}
+
+TEST_F(LeakyBucketTest, ChangeDrainRate) {
+  QuicBandwidth draining_rate = QuicBandwidth::FromBytesPerSecond(200000);
+  leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+  leaky_bucket_->Add(clock_.Now(), 2000);
+  EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(5),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+  draining_rate = draining_rate.Scale(0.5f);  // Cut drain rate in half.
+  leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+  EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+            leaky_bucket_->TimeRemaining(clock_.Now()));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/loss_detection_interface.cc b/net/quic/congestion_control/loss_detection_interface.cc
new file mode 100644
index 0000000..df2983f
--- /dev/null
+++ b/net/quic/congestion_control/loss_detection_interface.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/loss_detection_interface.h"
+
+#include "net/quic/congestion_control/tcp_loss_algorithm.h"
+#include "net/quic/congestion_control/time_loss_algorithm.h"
+
+namespace net {
+
+// Factory for loss detection algorithm.
+LossDetectionInterface* LossDetectionInterface::Create(
+    LossDetectionType loss_type) {
+  switch (loss_type) {
+    case kNack:
+      return new TCPLossAlgorithm();
+    case kTime:
+      return new TimeLossAlgorithm();
+  }
+  LOG(DFATAL) << "Unknown loss detection algorithm:" << loss_type;
+  return nullptr;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/loss_detection_interface.h b/net/quic/congestion_control/loss_detection_interface.h
new file mode 100644
index 0000000..5aaa51d
--- /dev/null
+++ b/net/quic/congestion_control/loss_detection_interface.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The pure virtual class for send side loss detection algorithm.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_LOSS_DETECTION_INTERFACE_H_
+#define NET_QUIC_CONGESTION_CONTROL_LOSS_DETECTION_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class QuicUnackedPacketMap;
+class RttStats;
+
+class NET_EXPORT_PRIVATE LossDetectionInterface {
+ public:
+  // Creates a TCP loss detector.
+  static LossDetectionInterface* Create(LossDetectionType loss_type);
+
+  virtual ~LossDetectionInterface() {}
+
+  virtual LossDetectionType GetLossDetectionType() const = 0;
+
+  // Called when a new ack arrives or the loss alarm fires.
+  virtual SequenceNumberSet DetectLostPackets(
+      const QuicUnackedPacketMap& unacked_packets,
+      const QuicTime& time,
+      QuicPacketSequenceNumber largest_observed,
+      const RttStats& rtt_stats) = 0;
+
+  // Get the time the LossDetectionAlgorithm wants to re-evaluate losses.
+  // Returns QuicTime::Zero if no alarm needs to be set.
+  virtual QuicTime GetLossTimeout() const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_LOSS_DETECTION_INTERFACE_H_
diff --git a/net/quic/congestion_control/pacing_sender.cc b/net/quic/congestion_control/pacing_sender.cc
new file mode 100644
index 0000000..20d2b3b
--- /dev/null
+++ b/net/quic/congestion_control/pacing_sender.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/pacing_sender.h"
+
+namespace net {
+
+PacingSender::PacingSender(SendAlgorithmInterface* sender,
+                           QuicTime::Delta alarm_granularity,
+                           uint32 initial_packet_burst)
+    : sender_(sender),
+      alarm_granularity_(alarm_granularity),
+      initial_packet_burst_(initial_packet_burst),
+      burst_tokens_(initial_packet_burst),
+      last_delayed_packet_sent_time_(QuicTime::Zero()),
+      next_packet_send_time_(QuicTime::Zero()),
+      was_last_send_delayed_(false),
+      has_valid_rtt_(false) {
+}
+
+PacingSender::~PacingSender() {}
+
+void PacingSender::SetFromConfig(const QuicConfig& config, bool is_server) {
+  // TODO(ianswett): Consider using the suggested RTT for pacing an initial
+  // response.
+  sender_->SetFromConfig(config, is_server);
+}
+
+void PacingSender::SetNumEmulatedConnections(int num_connections) {
+  sender_->SetNumEmulatedConnections(num_connections);
+}
+
+void PacingSender::OnIncomingQuicCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& feedback,
+      QuicTime feedback_receive_time) {
+  sender_->OnIncomingQuicCongestionFeedbackFrame(
+      feedback, feedback_receive_time);
+}
+
+void PacingSender::OnCongestionEvent(bool rtt_updated,
+                                     QuicByteCount bytes_in_flight,
+                                     const CongestionVector& acked_packets,
+                                     const CongestionVector& lost_packets) {
+  if (rtt_updated) {
+    has_valid_rtt_ = true;
+  }
+  sender_->OnCongestionEvent(
+      rtt_updated, bytes_in_flight, acked_packets, lost_packets);
+}
+
+bool PacingSender::OnPacketSent(
+    QuicTime sent_time,
+    QuicByteCount bytes_in_flight,
+    QuicPacketSequenceNumber sequence_number,
+    QuicByteCount bytes,
+    HasRetransmittableData has_retransmittable_data) {
+  // Only pace data packets once we have an updated RTT.
+  const bool in_flight =
+      sender_->OnPacketSent(sent_time, bytes_in_flight, sequence_number,
+                            bytes, has_retransmittable_data);
+  if (has_retransmittable_data != HAS_RETRANSMITTABLE_DATA || !has_valid_rtt_) {
+    return in_flight;
+  }
+  if (burst_tokens_ > 0) {
+    --burst_tokens_;
+    was_last_send_delayed_ = false;
+    last_delayed_packet_sent_time_ = QuicTime::Zero();
+    next_packet_send_time_ = QuicTime::Zero();
+    return in_flight;
+  }
+  // The next packet should be sent as soon as the current packets has
+  // been transferred.  We pace at twice the rate of the underlying
+  // sender's bandwidth estimate during slow start and 1.25x during congestion
+  // avoidance to ensure pacing doesn't prevent us from filling the window.
+  const float kPacingAggression = sender_->InSlowStart() ? 2 : 1.25;
+  QuicTime::Delta delay =
+      BandwidthEstimate().Scale(kPacingAggression).TransferTime(bytes);
+  // If the last send was delayed, and the alarm took a long time to get
+  // invoked, allow the connection to make up for lost time.
+  if (was_last_send_delayed_) {
+    next_packet_send_time_ = next_packet_send_time_.Add(delay);
+    // The send was application limited if it takes longer than the
+    // pacing delay between sent packets.
+    const bool application_limited =
+        last_delayed_packet_sent_time_.IsInitialized() &&
+        sent_time > last_delayed_packet_sent_time_.Add(delay);
+    const bool making_up_for_lost_time = next_packet_send_time_ <= sent_time;
+    // As long as we're making up time and not application limited,
+    // continue to consider the packets delayed, allowing the packets to be
+    // sent immediately.
+    if (making_up_for_lost_time && !application_limited) {
+      last_delayed_packet_sent_time_ = sent_time;
+    } else {
+      was_last_send_delayed_ = false;
+      last_delayed_packet_sent_time_ = QuicTime::Zero();
+    }
+  } else {
+    next_packet_send_time_ =
+        QuicTime::Max(next_packet_send_time_.Add(delay),
+                      sent_time.Add(delay).Subtract(alarm_granularity_));
+  }
+  return in_flight;
+}
+
+void PacingSender::OnRetransmissionTimeout(bool packets_retransmitted) {
+  sender_->OnRetransmissionTimeout(packets_retransmitted);
+}
+
+void PacingSender::RevertRetransmissionTimeout() {
+  sender_->RevertRetransmissionTimeout();
+}
+
+QuicTime::Delta PacingSender::TimeUntilSend(
+      QuicTime now,
+      QuicByteCount bytes_in_flight,
+      HasRetransmittableData has_retransmittable_data) const {
+  QuicTime::Delta time_until_send =
+      sender_->TimeUntilSend(now, bytes_in_flight, has_retransmittable_data);
+  if (!has_valid_rtt_) {
+    // Don't pace if we don't have an updated RTT estimate.
+    return time_until_send;
+  }
+  if (bytes_in_flight == 0) {
+    // Add more burst tokens anytime the connection is entering quiescence.
+    burst_tokens_ = initial_packet_burst_;
+  }
+  if (burst_tokens_ > 0) {
+    // Don't pace if we have burst tokens available.
+    return time_until_send;
+  }
+
+  if (!time_until_send.IsZero()) {
+    DCHECK(time_until_send.IsInfinite());
+    // The underlying sender prevents sending.
+    return time_until_send;
+  }
+
+  if (has_retransmittable_data == NO_RETRANSMITTABLE_DATA) {
+    // Don't pace ACK packets, since they do not count against CWND and do not
+    // cause CWND to grow.
+    return QuicTime::Delta::Zero();
+  }
+
+  // If the next send time is within the alarm granularity, send immediately.
+  if (next_packet_send_time_ > now.Add(alarm_granularity_)) {
+    DVLOG(1) << "Delaying packet: "
+             << next_packet_send_time_.Subtract(now).ToMicroseconds();
+    was_last_send_delayed_ = true;
+    return next_packet_send_time_.Subtract(now);
+  }
+
+  DVLOG(1) << "Sending packet now";
+  return QuicTime::Delta::Zero();
+}
+
+QuicBandwidth PacingSender::BandwidthEstimate() const {
+  return sender_->BandwidthEstimate();
+}
+
+bool PacingSender::HasReliableBandwidthEstimate() const {
+  return sender_->HasReliableBandwidthEstimate();
+}
+
+QuicTime::Delta PacingSender::RetransmissionDelay() const {
+  return sender_->RetransmissionDelay();
+}
+
+QuicByteCount PacingSender::GetCongestionWindow() const {
+  return sender_->GetCongestionWindow();
+}
+
+bool PacingSender::InSlowStart() const {
+  return sender_->InSlowStart();
+}
+
+bool PacingSender::InRecovery() const {
+  return sender_->InRecovery();
+}
+
+QuicByteCount PacingSender::GetSlowStartThreshold() const {
+  return sender_->GetSlowStartThreshold();
+}
+
+CongestionControlType PacingSender::GetCongestionControlType() const {
+  return sender_->GetCongestionControlType();
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/pacing_sender.h b/net/quic/congestion_control/pacing_sender.h
new file mode 100644
index 0000000..f61ae75
--- /dev/null
+++ b/net/quic/congestion_control/pacing_sender.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A send algorithm which adds pacing on top of an another send algorithm.
+// It uses the underlying sender's bandwidth estimate to determine the
+// pacing rate to be used.  It also takes into consideration the expected
+// resolution of the underlying alarm mechanism to ensure that alarms are
+// not set too aggressively, and to smooth out variations.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_PACING_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_PACING_SENDER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE PacingSender : public SendAlgorithmInterface {
+ public:
+  // Create a PacingSender to wrap the specified sender.  |alarm_granularity|
+  // indicates to the pacer to send that far into the future, since it should
+  // not expect a callback before that time delta.  |initial_packet_burst| is
+  // the number of packets sent without pacing after quiescence.
+  PacingSender(SendAlgorithmInterface* sender,
+               QuicTime::Delta alarm_granularity,
+               uint32 initial_packet_burst);
+  virtual ~PacingSender();
+
+  // SendAlgorithmInterface methods.
+  virtual void SetFromConfig(const QuicConfig& config, bool is_server) OVERRIDE;
+  virtual void SetNumEmulatedConnections(int num_connections) OVERRIDE;
+  virtual void OnIncomingQuicCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& feedback,
+      QuicTime feedback_receive_time) OVERRIDE;
+  virtual void OnCongestionEvent(bool rtt_updated,
+                                 QuicByteCount bytes_in_flight,
+                                 const CongestionVector& acked_packets,
+                                 const CongestionVector& lost_packets) OVERRIDE;
+  virtual bool OnPacketSent(QuicTime sent_time,
+                            QuicByteCount bytes_in_flight,
+                            QuicPacketSequenceNumber sequence_number,
+                            QuicByteCount bytes,
+                            HasRetransmittableData is_retransmittable) OVERRIDE;
+  virtual void OnRetransmissionTimeout(bool packets_retransmitted) OVERRIDE;
+  virtual void RevertRetransmissionTimeout() OVERRIDE;
+  virtual QuicTime::Delta TimeUntilSend(
+      QuicTime now,
+      QuicByteCount bytes_in_flight,
+      HasRetransmittableData has_retransmittable_data) const OVERRIDE;
+  virtual QuicBandwidth BandwidthEstimate() const OVERRIDE;
+  virtual bool HasReliableBandwidthEstimate() const OVERRIDE;
+  virtual QuicTime::Delta RetransmissionDelay() const OVERRIDE;
+  virtual QuicByteCount GetCongestionWindow() const OVERRIDE;
+  virtual bool InSlowStart() const OVERRIDE;
+  virtual bool InRecovery() const OVERRIDE;
+  virtual QuicByteCount GetSlowStartThreshold() const OVERRIDE;
+  virtual CongestionControlType GetCongestionControlType() const OVERRIDE;
+
+ private:
+  scoped_ptr<SendAlgorithmInterface> sender_;  // Underlying sender.
+  QuicTime::Delta alarm_granularity_;
+  uint32 initial_packet_burst_;
+  mutable uint32 burst_tokens_;
+  // Send time of the last packet considered delayed.
+  QuicTime last_delayed_packet_sent_time_;
+  QuicTime next_packet_send_time_;  // When can the next packet be sent.
+  mutable bool was_last_send_delayed_;  // True when the last send was delayed.
+  bool has_valid_rtt_;  // True if we have at least one RTT update.
+
+  DISALLOW_COPY_AND_ASSIGN(PacingSender);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_PACING_SENDER_H_
diff --git a/net/quic/congestion_control/pacing_sender_test.cc b/net/quic/congestion_control/pacing_sender_test.cc
new file mode 100644
index 0000000..768cc22
--- /dev/null
+++ b/net/quic/congestion_control/pacing_sender_test.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/pacing_sender.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+
+const QuicByteCount kBytesInFlight = 1024;
+
+class PacingSenderTest : public ::testing::Test {
+ protected:
+  PacingSenderTest()
+      : zero_time_(QuicTime::Delta::Zero()),
+        infinite_time_(QuicTime::Delta::Infinite()),
+        sequence_number_(1),
+        mock_sender_(new StrictMock<MockSendAlgorithm>()),
+        pacing_sender_(new PacingSender(mock_sender_,
+                                        QuicTime::Delta::FromMilliseconds(1),
+                                        0)) {
+    // Pick arbitrary time.
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(9));
+  }
+
+  virtual ~PacingSenderTest() {}
+
+  void CheckPacketIsSentImmediately() {
+    // In order for the packet to be sendable, the underlying sender must
+    // permit it to be sent immediately.
+    for (int i = 0; i < 2; ++i) {
+      EXPECT_CALL(*mock_sender_, TimeUntilSend(clock_.Now(),
+                                               kBytesInFlight,
+                                               HAS_RETRANSMITTABLE_DATA))
+          .WillOnce(Return(zero_time_));
+      // Verify that the packet can be sent immediately.
+      EXPECT_EQ(zero_time_,
+                pacing_sender_->TimeUntilSend(clock_.Now(),
+                                              kBytesInFlight,
+                                              HAS_RETRANSMITTABLE_DATA));
+    }
+
+    // Actually send the packet.
+    EXPECT_CALL(*mock_sender_,
+                OnPacketSent(clock_.Now(), kBytesInFlight, sequence_number_,
+                             kMaxPacketSize, HAS_RETRANSMITTABLE_DATA));
+    pacing_sender_->OnPacketSent(clock_.Now(), kBytesInFlight,
+                                 sequence_number_++, kMaxPacketSize,
+                                 HAS_RETRANSMITTABLE_DATA);
+  }
+
+  void CheckAckIsSentImmediately() {
+    // In order for the ack to be sendable, the underlying sender must
+    // permit it to be sent immediately.
+    EXPECT_CALL(*mock_sender_, TimeUntilSend(clock_.Now(),
+                                             0,
+                                             NO_RETRANSMITTABLE_DATA))
+        .WillOnce(Return(zero_time_));
+    // Verify that the ACK can be sent immediately.
+    EXPECT_EQ(zero_time_,
+              pacing_sender_->TimeUntilSend(clock_.Now(),
+                                            0,
+                                            NO_RETRANSMITTABLE_DATA));
+
+    // Actually send the packet.
+    EXPECT_CALL(*mock_sender_,
+                OnPacketSent(clock_.Now(), 0, sequence_number_,
+                             kMaxPacketSize, NO_RETRANSMITTABLE_DATA));
+    pacing_sender_->OnPacketSent(clock_.Now(), 0,
+                                 sequence_number_++, kMaxPacketSize,
+                                 NO_RETRANSMITTABLE_DATA);
+  }
+
+  void CheckPacketIsDelayed(QuicTime::Delta delay) {
+    // In order for the packet to be sendable, the underlying sender must
+    // permit it to be sent immediately.
+    for (int i = 0; i < 2; ++i) {
+      EXPECT_CALL(*mock_sender_, TimeUntilSend(clock_.Now(),
+                                               kBytesInFlight,
+                                               HAS_RETRANSMITTABLE_DATA))
+          .WillOnce(Return(zero_time_));
+      // Verify that the packet is delayed.
+      EXPECT_EQ(delay.ToMicroseconds(),
+                pacing_sender_->TimeUntilSend(
+                    clock_.Now(), kBytesInFlight,
+                    HAS_RETRANSMITTABLE_DATA).ToMicroseconds());
+    }
+  }
+
+  const QuicTime::Delta zero_time_;
+  const QuicTime::Delta infinite_time_;
+  MockClock clock_;
+  QuicPacketSequenceNumber sequence_number_;
+  StrictMock<MockSendAlgorithm>* mock_sender_;
+  scoped_ptr<PacingSender> pacing_sender_;
+};
+
+TEST_F(PacingSenderTest, NoSend) {
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_CALL(*mock_sender_, TimeUntilSend(clock_.Now(),
+                                             kBytesInFlight,
+                                             HAS_RETRANSMITTABLE_DATA))
+        .WillOnce(Return(infinite_time_));
+    EXPECT_EQ(infinite_time_,
+              pacing_sender_->TimeUntilSend(clock_.Now(),
+                                            kBytesInFlight,
+                                            HAS_RETRANSMITTABLE_DATA));
+  }
+}
+
+TEST_F(PacingSenderTest, SendNow) {
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_CALL(*mock_sender_, TimeUntilSend(clock_.Now(),
+                                             kBytesInFlight,
+                                             HAS_RETRANSMITTABLE_DATA))
+        .WillOnce(Return(zero_time_));
+    EXPECT_EQ(zero_time_,
+              pacing_sender_->TimeUntilSend(clock_.Now(),
+                                            kBytesInFlight,
+                                            HAS_RETRANSMITTABLE_DATA));
+  }
+}
+
+TEST_F(PacingSenderTest, VariousSending) {
+  // Start the test in slow start.
+  EXPECT_CALL(*mock_sender_, InSlowStart()).WillRepeatedly(Return(true));
+
+  // Configure bandwith of 1 packet per 2 ms, for which the pacing rate
+  // will be 1 packet per 1 ms.
+  EXPECT_CALL(*mock_sender_, BandwidthEstimate())
+      .WillRepeatedly(Return(QuicBandwidth::FromBytesAndTimeDelta(
+          kMaxPacketSize, QuicTime::Delta::FromMilliseconds(2))));
+
+  // Send a whole pile of packets, and verify that they are not paced.
+  for (int i = 0 ; i < 1000; ++i) {
+    CheckPacketIsSentImmediately();
+  }
+
+  // Now update the RTT and verify that packets are actually paced.
+  EXPECT_CALL(*mock_sender_, OnCongestionEvent(true, kBytesInFlight, _, _));
+  SendAlgorithmInterface::CongestionVector empty_map;
+  pacing_sender_->OnCongestionEvent(true, kBytesInFlight, empty_map, empty_map);
+
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+
+  // The first packet was a "make up", then we sent two packets "into the
+  // future", so the delay should be 2.
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+
+  // Wake up on time.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(2));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+  CheckAckIsSentImmediately();
+
+  // Wake up late.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(4));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+
+  // Wake up really late.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(8));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+
+  // Wake up really late again, but application pause partway through.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(8));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+
+  // Wake up too early.
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+
+  // Wake up early, but after enough time has passed to permit a send.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+}
+
+TEST_F(PacingSenderTest, CongestionAvoidanceSending) {
+  // Start the test in congestion avoidance.
+  EXPECT_CALL(*mock_sender_, InSlowStart()).WillRepeatedly(Return(false));
+
+  // Configure bandwith of 1 packet per 2 ms, for which the pacing rate
+  // will be 1 packet per 1 ms.
+  EXPECT_CALL(*mock_sender_, BandwidthEstimate())
+      .WillRepeatedly(Return(QuicBandwidth::FromBytesAndTimeDelta(
+          kMaxPacketSize, QuicTime::Delta::FromMilliseconds(2))));
+
+  // Send a whole pile of packets, and verify that they are not paced.
+  for (int i = 0 ; i < 1000; ++i) {
+    CheckPacketIsSentImmediately();
+  }
+
+  // Now update the RTT and verify that packets are actually paced.
+  EXPECT_CALL(*mock_sender_, OnCongestionEvent(true, kBytesInFlight, _, _));
+  SendAlgorithmInterface::CongestionVector empty_map;
+  pacing_sender_->OnCongestionEvent(true, kBytesInFlight, empty_map, empty_map);
+
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+
+  // The first packet was a "make up", then we sent two packets "into the
+  // future", so the delay should be 2200us.
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2200));
+
+  // Wake up on time.
+  clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(2200));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(1600));
+  CheckAckIsSentImmediately();
+
+  // Wake up late.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(4));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2400));
+
+  // Wake up really late.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(8));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2400));
+
+  // Wake up really late again, but application pause partway through.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(8));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2200));
+
+  // Wake up too early.
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2200));
+
+  // Wake up early, but after enough time has passed to permit a send.
+  clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(1200));
+  CheckPacketIsSentImmediately();
+  CheckPacketIsDelayed(QuicTime::Delta::FromMicroseconds(2600));
+}
+
+TEST_F(PacingSenderTest, InitialBurst) {
+  pacing_sender_.reset();
+  mock_sender_ = new StrictMock<MockSendAlgorithm>();
+  pacing_sender_.reset(new PacingSender(mock_sender_,
+                                        QuicTime::Delta::FromMilliseconds(1),
+                                        10));
+  // Start the test in slow start.
+  EXPECT_CALL(*mock_sender_, InSlowStart()).WillRepeatedly(Return(true));
+
+  // Configure bandwith of 1 packet per 2 ms, for which the pacing rate
+  // will be 1 packet per 1 ms.
+  EXPECT_CALL(*mock_sender_, BandwidthEstimate())
+      .WillRepeatedly(Return(QuicBandwidth::FromBytesAndTimeDelta(
+          kMaxPacketSize, QuicTime::Delta::FromMilliseconds(2))));
+
+  // Update the RTT and verify that the first 10 packets aren't paced.
+  EXPECT_CALL(*mock_sender_, OnCongestionEvent(true, kBytesInFlight, _, _));
+  SendAlgorithmInterface::CongestionVector empty_map;
+  pacing_sender_->OnCongestionEvent(true, kBytesInFlight, empty_map, empty_map);
+
+  // Send 10 packets, and verify that they are not paced.
+  for (int i = 0 ; i < 10; ++i) {
+    CheckPacketIsSentImmediately();
+  }
+
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+
+  // The first packet was a "make up", then we sent two packets "into the
+  // future", so the delay should be 2.
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  CheckPacketIsSentImmediately();
+
+  // Next time TimeUntilSend is called with no bytes in flight, the tokens
+  // should be refilled and there should be no delay.
+  EXPECT_CALL(*mock_sender_,
+              TimeUntilSend(clock_.Now(),
+                            0,
+                            HAS_RETRANSMITTABLE_DATA)).
+      WillOnce(Return(zero_time_));
+  EXPECT_EQ(zero_time_,
+            pacing_sender_->TimeUntilSend(clock_.Now(),
+                                          0,
+                                          HAS_RETRANSMITTABLE_DATA));
+  for (int i = 0 ; i < 10; ++i) {
+    CheckPacketIsSentImmediately();
+  }
+
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+  CheckPacketIsSentImmediately();
+
+  CheckPacketIsDelayed(QuicTime::Delta::FromMilliseconds(2));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/receive_algorithm_interface.cc b/net/quic/congestion_control/receive_algorithm_interface.cc
new file mode 100644
index 0000000..72164ef
--- /dev/null
+++ b/net/quic/congestion_control/receive_algorithm_interface.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+
+#include "net/quic/congestion_control/tcp_receiver.h"
+
+namespace net {
+
+// Factory for receive side congestion control algorithm.
+ReceiveAlgorithmInterface* ReceiveAlgorithmInterface::Create(
+    CongestionFeedbackType type) {
+  switch (type) {
+    case kTCP:
+      return new TcpReceiver();
+  }
+  return nullptr;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/receive_algorithm_interface.h b/net/quic/congestion_control/receive_algorithm_interface.h
new file mode 100644
index 0000000..cd96b02
--- /dev/null
+++ b/net/quic/congestion_control/receive_algorithm_interface.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The pure virtual class for receive side congestion algorithm.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
+#define NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE ReceiveAlgorithmInterface {
+ public:
+  static ReceiveAlgorithmInterface* Create(CongestionFeedbackType type);
+
+  virtual ~ReceiveAlgorithmInterface() {}
+
+  // Returns false if no QuicCongestionFeedbackFrame block is needed.
+  // Otherwise fills in feedback and return true.
+  virtual bool GenerateCongestionFeedback(
+      QuicCongestionFeedbackFrame* feedback) = 0;
+
+  // Should be called for each incoming packet.
+  // bytes: is the packet size in bytes including IP headers.
+  // sequence_number: is the unique sequence number from the QUIC packet header.
+  // timestamp: is the sent timestamp from the QUIC packet header.
+  virtual void RecordIncomingPacket(QuicByteCount bytes,
+                                    QuicPacketSequenceNumber sequence_number,
+                                    QuicTime timestamp) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
diff --git a/net/quic/congestion_control/rtt_stats.cc b/net/quic/congestion_control/rtt_stats.cc
new file mode 100644
index 0000000..b03686d
--- /dev/null
+++ b/net/quic/congestion_control/rtt_stats.cc
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/rtt_stats.h"
+
+#include <complex>  // std::abs
+
+using std::max;
+
+namespace net {
+
+namespace {
+
+// Default initial rtt used before any samples are received.
+const int kInitialRttMs = 100;
+const float kAlpha = 0.125f;
+const float kOneMinusAlpha = (1 - kAlpha);
+const float kBeta = 0.25f;
+const float kOneMinusBeta = (1 - kBeta);
+const float kHalfWindow = 0.5f;
+const float kQuarterWindow = 0.25f;
+
+}  // namespace
+
+RttStats::RttStats()
+    : latest_rtt_(QuicTime::Delta::Zero()),
+      min_rtt_(QuicTime::Delta::Zero()),
+      smoothed_rtt_(QuicTime::Delta::Zero()),
+      mean_deviation_(QuicTime::Delta::Zero()),
+      initial_rtt_us_(kInitialRttMs * base::Time::kMicrosecondsPerMillisecond),
+      num_min_rtt_samples_remaining_(0),
+      recent_min_rtt_window_(QuicTime::Delta::Infinite()) {}
+
+bool RttStats::HasUpdates() const {
+  return !smoothed_rtt_.IsZero();
+}
+
+void RttStats::SampleNewRecentMinRtt(uint32 num_samples) {
+  num_min_rtt_samples_remaining_ = num_samples;
+  new_min_rtt_ = RttSample();
+}
+
+void RttStats::ExpireSmoothedMetrics() {
+  mean_deviation_ =
+      max(mean_deviation_,
+          QuicTime::Delta::FromMicroseconds(
+              std::abs(smoothed_rtt_.Subtract(latest_rtt_).ToMicroseconds())));
+  smoothed_rtt_ = max(smoothed_rtt_, latest_rtt_);
+}
+
+// Updates the RTT based on a new sample.
+void RttStats::UpdateRtt(QuicTime::Delta send_delta,
+                         QuicTime::Delta ack_delay,
+                         QuicTime now) {
+  QuicTime::Delta rtt_sample(QuicTime::Delta::Zero());
+  if (send_delta > ack_delay) {
+    rtt_sample = send_delta.Subtract(ack_delay);
+  } else if (!HasUpdates()) {
+    // Even though we received information from the peer suggesting
+    // an invalid (negative) RTT, we can use the send delta as an
+    // approximation until we get a better estimate.
+    rtt_sample = send_delta;
+  }
+
+  if (rtt_sample.IsInfinite() || rtt_sample.IsZero()) {
+    DVLOG(1) << "Ignoring rtt, because it's "
+             << (rtt_sample.IsZero() ? "Zero" : "Infinite");
+    return;
+  }
+  // RTT can't be negative.
+  DCHECK_LT(0, rtt_sample.ToMicroseconds());
+
+  latest_rtt_ = rtt_sample;
+  // First time call or link delay decreases.
+  if (min_rtt_.IsZero() || min_rtt_ > rtt_sample) {
+    min_rtt_ = rtt_sample;
+  }
+  UpdateRecentMinRtt(rtt_sample, now);
+  // First time call.
+  if (!HasUpdates()) {
+    smoothed_rtt_ = rtt_sample;
+    mean_deviation_ = QuicTime::Delta::FromMicroseconds(
+        rtt_sample.ToMicroseconds() / 2);
+  } else {
+    mean_deviation_ = QuicTime::Delta::FromMicroseconds(
+        kOneMinusBeta * mean_deviation_.ToMicroseconds() +
+        kBeta * std::abs(smoothed_rtt_.Subtract(rtt_sample).ToMicroseconds()));
+    smoothed_rtt_ = smoothed_rtt_.Multiply(kOneMinusAlpha).Add(
+        rtt_sample.Multiply(kAlpha));
+    DVLOG(1) << " smoothed_rtt(us):" << smoothed_rtt_.ToMicroseconds()
+             << " mean_deviation(us):" << mean_deviation_.ToMicroseconds();
+  }
+}
+
+void RttStats::UpdateRecentMinRtt(QuicTime::Delta rtt_sample, QuicTime now) {
+  // Recent min_rtt update.
+  if (num_min_rtt_samples_remaining_ > 0) {
+    --num_min_rtt_samples_remaining_;
+    if (new_min_rtt_.rtt.IsZero() || rtt_sample <= new_min_rtt_.rtt) {
+      new_min_rtt_ = RttSample(rtt_sample, now);
+    }
+    if (num_min_rtt_samples_remaining_ == 0) {
+      quarter_window_rtt_ = half_window_rtt_ = recent_min_rtt_ = new_min_rtt_;
+    }
+  }
+
+  // Update the three recent rtt samples.
+  if (recent_min_rtt_.rtt.IsZero() || rtt_sample <= recent_min_rtt_.rtt) {
+    recent_min_rtt_ = RttSample(rtt_sample, now);
+    quarter_window_rtt_ = half_window_rtt_ = recent_min_rtt_;
+  } else if (rtt_sample <= half_window_rtt_.rtt) {
+    half_window_rtt_ = RttSample(rtt_sample, now);
+    quarter_window_rtt_ = half_window_rtt_;
+  } else if (rtt_sample <= quarter_window_rtt_.rtt) {
+    quarter_window_rtt_ = RttSample(rtt_sample, now);
+  }
+
+  // Expire old min rtt samples.
+  if (recent_min_rtt_.time < now.Subtract(recent_min_rtt_window_)) {
+    recent_min_rtt_ = half_window_rtt_;
+    half_window_rtt_ = quarter_window_rtt_;
+    quarter_window_rtt_ = RttSample(rtt_sample, now);
+  } else if (half_window_rtt_.time <
+      now.Subtract(recent_min_rtt_window_.Multiply(kHalfWindow))) {
+    half_window_rtt_ = quarter_window_rtt_;
+    quarter_window_rtt_ = RttSample(rtt_sample, now);
+  } else if (quarter_window_rtt_.time <
+      now.Subtract(recent_min_rtt_window_.Multiply(kQuarterWindow))) {
+    quarter_window_rtt_ = RttSample(rtt_sample, now);
+  }
+}
+
+QuicTime::Delta RttStats::SmoothedRtt() const {
+  if (!HasUpdates()) {
+    return QuicTime::Delta::FromMicroseconds(initial_rtt_us_);
+  }
+  return smoothed_rtt_;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/rtt_stats.h b/net/quic/congestion_control/rtt_stats.h
new file mode 100644
index 0000000..5e01687
--- /dev/null
+++ b/net/quic/congestion_control/rtt_stats.h
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A convenience class to store rtt samples and calculate smoothed rtt.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_RTT_STATS_H_
+#define NET_QUIC_CONGESTION_CONTROL_RTT_STATS_H_
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+namespace test {
+class RttStatsPeer;
+}  // namespace test
+
+class NET_EXPORT_PRIVATE RttStats {
+ public:
+  RttStats();
+
+  // Returns true if any RTT measurements have been made.
+  bool HasUpdates() const;
+
+  // Updates the RTT from an incoming ack which is received |send_delta| after
+  // the packet is sent and the peer reports the ack being delayed |ack_delay|.
+  void UpdateRtt(QuicTime::Delta send_delta,
+                 QuicTime::Delta ack_delay,
+                 QuicTime now);
+
+  // Causes the smoothed_rtt to be increased to the latest_rtt if the latest_rtt
+  // is larger. The mean deviation is increased to the most recent deviation if
+  // it's larger.
+  void ExpireSmoothedMetrics();
+
+  // Forces RttStats to sample a new recent min rtt within the next
+  // |num_samples| UpdateRtt calls.
+  void SampleNewRecentMinRtt(uint32 num_samples);
+
+  QuicTime::Delta SmoothedRtt() const;
+
+  int64 initial_rtt_us() const {
+    return initial_rtt_us_;
+  }
+
+  // Sets an initial RTT to be used for SmoothedRtt before any RTT updates.
+  void set_initial_rtt_us(int64 initial_rtt_us) {
+    initial_rtt_us_ = initial_rtt_us;
+  }
+
+  QuicTime::Delta latest_rtt() const {
+    return latest_rtt_;
+  }
+
+  // Returns the min_rtt for the entire connection.
+  QuicTime::Delta min_rtt() const {
+    return min_rtt_;
+  }
+
+  // Returns the min_rtt since SampleNewRecentMinRtt has been called, or the
+  // min_rtt for the entire connection if SampleNewMinRtt was never called.
+  QuicTime::Delta recent_min_rtt() const {
+    return recent_min_rtt_.rtt;
+  }
+
+  QuicTime::Delta mean_deviation() const {
+    return mean_deviation_;
+  }
+
+  // Sets how old a recent min rtt sample can be.
+  void set_recent_min_rtt_window(QuicTime::Delta recent_min_rtt_window) {
+    recent_min_rtt_window_ = recent_min_rtt_window;
+  }
+
+ private:
+  friend class test::RttStatsPeer;
+
+  // Used to track a sampled RTT window.
+  struct RttSample {
+    RttSample() : rtt(QuicTime::Delta::Zero()), time(QuicTime::Zero()) { }
+    RttSample(QuicTime::Delta rtt, QuicTime time) : rtt(rtt), time(time) { }
+
+    QuicTime::Delta rtt;
+    QuicTime time;  // Time the rtt sample was recorded.
+  };
+
+  // Implements the resampling algorithm and the windowed min rtt algorithm.
+  void UpdateRecentMinRtt(QuicTime::Delta rtt_sample, QuicTime now);
+
+  QuicTime::Delta latest_rtt_;
+  QuicTime::Delta min_rtt_;
+  QuicTime::Delta smoothed_rtt_;
+  // Mean RTT deviation during this session.
+  // Approximation of standard deviation, the error is roughly 1.25 times
+  // larger than the standard deviation, for a normally distributed signal.
+  QuicTime::Delta mean_deviation_;
+  int64 initial_rtt_us_;
+
+  RttSample new_min_rtt_;
+  uint32 num_min_rtt_samples_remaining_;
+
+  // State variables for Kathleen Nichols MinRTT algorithm.
+  QuicTime::Delta recent_min_rtt_window_;
+  RttSample recent_min_rtt_;  // a in the windowed algorithm.
+  RttSample half_window_rtt_;  // b in the sampled algorithm.
+  RttSample quarter_window_rtt_;  // c in the sampled algorithm.
+
+  DISALLOW_COPY_AND_ASSIGN(RttStats);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_RTT_STATS_H_
diff --git a/net/quic/congestion_control/rtt_stats_test.cc b/net/quic/congestion_control/rtt_stats_test.cc
new file mode 100644
index 0000000..6b5ff7d
--- /dev/null
+++ b/net/quic/congestion_control/rtt_stats_test.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/rtt_stats.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class RttStatsPeer {
+ public:
+  static QuicTime::Delta GetHalfWindowRtt(const RttStats* rtt_stats) {
+    return rtt_stats->half_window_rtt_.rtt;
+  }
+
+  static QuicTime::Delta GetQuarterWindowRtt(const RttStats* rtt_stats) {
+    return rtt_stats->quarter_window_rtt_.rtt;
+  }
+};
+
+class RttStatsTest : public ::testing::Test {
+ protected:
+  RttStats rtt_stats_;
+};
+
+TEST_F(RttStatsTest, MinRtt) {
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(100),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100),
+            rtt_stats_.recent_min_rtt());
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(10),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero().Add(
+                           QuicTime::Delta::FromMilliseconds(10)));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(50),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero().Add(
+                           QuicTime::Delta::FromMilliseconds(20)));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(50),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero().Add(
+                           QuicTime::Delta::FromMilliseconds(30)));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(50),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero().Add(
+                           QuicTime::Delta::FromMilliseconds(40)));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+}
+
+TEST_F(RttStatsTest, RecentMinRtt) {
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(10),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+
+  rtt_stats_.SampleNewRecentMinRtt(4);
+  for (int i = 0; i < 3; ++i) {
+    rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(50),
+                       QuicTime::Delta::Zero(),
+                       QuicTime::Zero());
+    EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+    EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+              rtt_stats_.recent_min_rtt());
+  }
+  rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(50),
+                        QuicTime::Delta::Zero(),
+                        QuicTime::Zero());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(50), rtt_stats_.recent_min_rtt());
+}
+
+TEST_F(RttStatsTest, WindowedRecentMinRtt) {
+  // Set the window to 99ms, so 25ms is more than a quarter rtt.
+  rtt_stats_.set_recent_min_rtt_window(QuicTime::Delta::FromMilliseconds(99));
+
+  QuicTime now = QuicTime::Zero();
+  QuicTime::Delta rtt_sample = QuicTime::Delta::FromMilliseconds(10);
+  rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.recent_min_rtt());
+
+  // Gradually increase the rtt samples and ensure the recent_min_rtt starts
+  // rising.
+  for (int i = 0; i < 8; ++i) {
+    now = now.Add(QuicTime::Delta::FromMilliseconds(25));
+    rtt_sample = rtt_sample.Add(QuicTime::Delta::FromMilliseconds(10));
+    rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+    EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+    EXPECT_EQ(rtt_sample, RttStatsPeer::GetQuarterWindowRtt(&rtt_stats_));
+    EXPECT_EQ(rtt_sample.Subtract(QuicTime::Delta::FromMilliseconds(10)),
+              RttStatsPeer::GetHalfWindowRtt(&rtt_stats_));
+    if (i < 3) {
+      EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+                rtt_stats_.recent_min_rtt());
+    } else if (i < 5) {
+      EXPECT_EQ(QuicTime::Delta::FromMilliseconds(30),
+                rtt_stats_.recent_min_rtt());
+    } else if (i < 7) {
+      EXPECT_EQ(QuicTime::Delta::FromMilliseconds(50),
+                rtt_stats_.recent_min_rtt());
+    } else {
+      EXPECT_EQ(QuicTime::Delta::FromMilliseconds(70),
+                rtt_stats_.recent_min_rtt());
+    }
+  }
+
+  // A new quarter rtt low sets that, but nothing else.
+  rtt_sample = rtt_sample.Subtract(QuicTime::Delta::FromMilliseconds(5));
+  rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetQuarterWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample.Subtract(QuicTime::Delta::FromMilliseconds(5)),
+            RttStatsPeer::GetHalfWindowRtt(&rtt_stats_));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(70),
+            rtt_stats_.recent_min_rtt());
+
+  // A new half rtt low sets that and the quarter rtt low.
+  rtt_sample = rtt_sample.Subtract(QuicTime::Delta::FromMilliseconds(15));
+  rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetQuarterWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetHalfWindowRtt(&rtt_stats_));
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(70),
+            rtt_stats_.recent_min_rtt());
+
+  // A new full window loss sets the recent_min_rtt, but not min_rtt.
+  rtt_sample = QuicTime::Delta::FromMilliseconds(65);
+  rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), rtt_stats_.min_rtt());
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetQuarterWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetHalfWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample, rtt_stats_.recent_min_rtt());
+
+  // A new all time low sets both the min_rtt and the recent_min_rtt.
+  rtt_sample = QuicTime::Delta::FromMilliseconds(5);
+  rtt_stats_.UpdateRtt(rtt_sample, QuicTime::Delta::Zero(), now);
+
+  EXPECT_EQ(rtt_sample, rtt_stats_.min_rtt());
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetQuarterWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample, RttStatsPeer::GetHalfWindowRtt(&rtt_stats_));
+  EXPECT_EQ(rtt_sample, rtt_stats_.recent_min_rtt());
+}
+
+TEST_F(RttStatsTest, ExpireSmoothedMetrics) {
+  QuicTime::Delta initial_rtt = QuicTime::Delta::FromMilliseconds(10);
+  rtt_stats_.UpdateRtt(initial_rtt, QuicTime::Delta::Zero(), QuicTime::Zero());
+  EXPECT_EQ(initial_rtt, rtt_stats_.min_rtt());
+  EXPECT_EQ(initial_rtt, rtt_stats_.recent_min_rtt());
+  EXPECT_EQ(initial_rtt, rtt_stats_.SmoothedRtt());
+
+  EXPECT_EQ(initial_rtt.Multiply(0.5), rtt_stats_.mean_deviation());
+
+  // Update once with a 20ms RTT.
+  QuicTime::Delta doubled_rtt = initial_rtt.Multiply(2);
+  rtt_stats_.UpdateRtt(doubled_rtt, QuicTime::Delta::Zero(), QuicTime::Zero());
+  EXPECT_EQ(initial_rtt.Multiply(1.125), rtt_stats_.SmoothedRtt());
+
+  // Expire the smoothed metrics, increasing smoothed rtt and mean deviation.
+  rtt_stats_.ExpireSmoothedMetrics();
+  EXPECT_EQ(doubled_rtt, rtt_stats_.SmoothedRtt());
+  EXPECT_EQ(initial_rtt.Multiply(0.875), rtt_stats_.mean_deviation());
+
+  // Now go back down to 5ms and expire the smoothed metrics, and ensure the
+  // mean deviation increases to 15ms.
+  QuicTime::Delta half_rtt = initial_rtt.Multiply(0.5);
+  rtt_stats_.UpdateRtt(half_rtt, QuicTime::Delta::Zero(), QuicTime::Zero());
+  EXPECT_GT(doubled_rtt, rtt_stats_.SmoothedRtt());
+  EXPECT_LT(initial_rtt, rtt_stats_.mean_deviation());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/send_algorithm_interface.cc b/net/quic/congestion_control/send_algorithm_interface.cc
new file mode 100644
index 0000000..7245e33
--- /dev/null
+++ b/net/quic/congestion_control/send_algorithm_interface.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class RttStats;
+
+// Factory for send side congestion control algorithm.
+SendAlgorithmInterface* SendAlgorithmInterface::Create(
+    const QuicClock* clock,
+    const RttStats* rtt_stats,
+    CongestionControlType congestion_control_type,
+    QuicConnectionStats* stats) {
+  switch (congestion_control_type) {
+    case kCubic:
+      return new TcpCubicSender(clock, rtt_stats,
+                                false /* don't use Reno */,
+                                kMaxTcpCongestionWindow, stats);
+    case kReno:
+      return new TcpCubicSender(clock, rtt_stats,
+                                true /* use Reno */,
+                                kMaxTcpCongestionWindow, stats);
+    case kBBR:
+      LOG(DFATAL) << "BbrTcpSender is not supported.";
+      return nullptr;
+  }
+  return nullptr;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/send_algorithm_interface.h b/net/quic/congestion_control/send_algorithm_interface.h
new file mode 100644
index 0000000..14383f8
--- /dev/null
+++ b/net/quic/congestion_control/send_algorithm_interface.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The pure virtual class for send side congestion control algorithm.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
+#define NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
+
+#include <algorithm>
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class RttStats;
+
+class NET_EXPORT_PRIVATE SendAlgorithmInterface {
+ public:
+  // A sorted vector of packets.
+  typedef std::vector<std::pair<QuicPacketSequenceNumber, TransmissionInfo>>
+      CongestionVector;
+
+  static SendAlgorithmInterface* Create(const QuicClock* clock,
+                                        const RttStats* rtt_stats,
+                                        CongestionControlType type,
+                                        QuicConnectionStats* stats);
+
+  virtual ~SendAlgorithmInterface() {}
+
+  virtual void SetFromConfig(const QuicConfig& config, bool is_server) = 0;
+
+  // Sets the number of connections to emulate when doing congestion control,
+  // particularly for congestion avoidance.  Can be set any time.
+  virtual void SetNumEmulatedConnections(int num_connections) = 0;
+
+  // Called when we receive congestion feedback from remote peer.
+  virtual void OnIncomingQuicCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& feedback,
+      QuicTime feedback_receive_time) = 0;
+
+  // Indicates an update to the congestion state, caused either by an incoming
+  // ack or loss event timeout.  |rtt_updated| indicates whether a new
+  // latest_rtt sample has been taken, |byte_in_flight| the bytes in flight
+  // prior to the congestion event.  |acked_packets| and |lost_packets| are
+  // any packets considered acked or lost as a result of the congestion event.
+  virtual void OnCongestionEvent(bool rtt_updated,
+                                 QuicByteCount bytes_in_flight,
+                                 const CongestionVector& acked_packets,
+                                 const CongestionVector& lost_packets) = 0;
+
+  // Inform that we sent |bytes| to the wire, and if the packet is
+  // retransmittable. Returns true if the packet should be tracked by the
+  // congestion manager and included in bytes_in_flight, false otherwise.
+  // |bytes_in_flight| is the number of bytes in flight before the packet was
+  // sent.
+  // Note: this function must be called for every packet sent to the wire.
+  virtual bool OnPacketSent(QuicTime sent_time,
+                            QuicByteCount bytes_in_flight,
+                            QuicPacketSequenceNumber sequence_number,
+                            QuicByteCount bytes,
+                            HasRetransmittableData is_retransmittable) = 0;
+
+  // Called when the retransmission timeout fires.  Neither OnPacketAbandoned
+  // nor OnPacketLost will be called for these packets.
+  virtual void OnRetransmissionTimeout(bool packets_retransmitted) = 0;
+
+  // Called when the last retransmission timeout was spurious.
+  virtual void RevertRetransmissionTimeout() = 0;
+
+  // Calculate the time until we can send the next packet.
+  virtual QuicTime::Delta TimeUntilSend(
+      QuicTime now,
+      QuicByteCount bytes_in_flight,
+      HasRetransmittableData has_retransmittable_data) const = 0;
+
+  // What's the current estimated bandwidth in bytes per second.
+  // Returns 0 when it does not have an estimate.
+  virtual QuicBandwidth BandwidthEstimate() const = 0;
+
+  // Returns true if the current bandwidth estimate is reliable.
+  virtual bool HasReliableBandwidthEstimate() const = 0;
+
+  // Get the send algorithm specific retransmission delay, called RTO in TCP,
+  // Note 1: the caller is responsible for sanity checking this value.
+  // Note 2: this will return zero if we don't have enough data for an estimate.
+  virtual QuicTime::Delta RetransmissionDelay() const = 0;
+
+  // Returns the size of the current congestion window in bytes.  Note, this is
+  // not the *available* window.  Some send algorithms may not use a congestion
+  // window and will return 0.
+  virtual QuicByteCount GetCongestionWindow() const = 0;
+
+  // Whether the send algorithm is currently in slow start.  When true, the
+  // BandwidthEstimate is expected to be too low.
+  virtual bool InSlowStart() const = 0;
+
+  // Whether the send algorithm is currently in recovery.
+  virtual bool InRecovery() const = 0;
+
+  // Returns the size of the slow start congestion window in bytes,
+  // aka ssthresh.  Some send algorithms do not define a slow start
+  // threshold and will return 0.
+  virtual QuicByteCount GetSlowStartThreshold() const = 0;
+
+  virtual CongestionControlType GetCongestionControlType() const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
diff --git a/net/quic/congestion_control/send_algorithm_simulator.cc b/net/quic/congestion_control/send_algorithm_simulator.cc
new file mode 100644
index 0000000..f08acff
--- /dev/null
+++ b/net/quic/congestion_control/send_algorithm_simulator.cc
@@ -0,0 +1,373 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/send_algorithm_simulator.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "net/quic/crypto/quic_random.h"
+
+using std::list;
+using std::make_pair;
+using std::max;
+using std::min;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+const QuicByteCount kPacketSize = 1200;
+
+}  // namespace
+
+SendAlgorithmSimulator::Sender::Sender(SendAlgorithmInterface* send_algorithm,
+                                       RttStats* rtt_stats)
+    : send_algorithm(send_algorithm),
+      rtt_stats(rtt_stats),
+      last_sent(0),
+      last_acked(0),
+      next_acked(1),
+      max_cwnd(0),
+      min_cwnd(100000),
+      max_cwnd_drop(0),
+      last_cwnd(0),
+      last_transfer_bandwidth(QuicBandwidth::Zero()),
+      last_transfer_loss_rate(0) {}
+
+SendAlgorithmSimulator::SendAlgorithmSimulator(
+    MockClock* clock,
+    QuicBandwidth bandwidth,
+    QuicTime::Delta rtt)
+    : clock_(clock),
+      lose_next_ack_(false),
+      forward_loss_rate_(0),
+      reverse_loss_rate_(0),
+      loss_correlation_(0),
+      bandwidth_(bandwidth),
+      rtt_(rtt),
+      buffer_size_(1000000),
+      delayed_ack_timer_(QuicTime::Delta::FromMilliseconds(100)) {
+  uint32 seed = base::RandInt(0, std::numeric_limits<int32>::max());
+  DVLOG(1) << "Seeding SendAlgorithmSimulator with " << seed;
+  simple_random_.set_seed(seed);
+}
+
+SendAlgorithmSimulator::~SendAlgorithmSimulator() {}
+
+void SendAlgorithmSimulator::AddTransfer(Sender* sender, size_t num_bytes) {
+  AddTransfer(sender, num_bytes, clock_->Now());
+}
+
+void SendAlgorithmSimulator::AddTransfer(
+    Sender* sender, size_t num_bytes, QuicTime start_time) {
+  pending_transfers_.push_back(Transfer(sender, num_bytes, start_time));
+  // Record initial stats from when the transfer begins.
+  pending_transfers_.back().sender->RecordStats();
+}
+
+void SendAlgorithmSimulator::TransferBytes() {
+  TransferBytes(kuint64max, QuicTime::Delta::Infinite());
+}
+
+void SendAlgorithmSimulator::TransferBytes(QuicByteCount max_bytes,
+                                           QuicTime::Delta max_time) {
+  const QuicTime end_time = max_time.IsInfinite() ?
+      QuicTime::Zero().Add(QuicTime::Delta::Infinite()) :
+      clock_->Now().Add(max_time);
+  QuicByteCount bytes_sent = 0;
+  while (!pending_transfers_.empty() &&
+         clock_->Now() < end_time &&
+         bytes_sent < max_bytes) {
+    // Determine the times of next send and of the next ack arrival.
+    PacketEvent send_event = NextSendEvent();
+    PacketEvent ack_event = NextAckEvent();
+    // If both times are infinite, fire a TLP.
+    if (ack_event.time_delta.IsInfinite() &&
+        send_event.time_delta.IsInfinite()) {
+      DVLOG(1) << "Both times are infinite, simulating a TLP.";
+      // TODO(ianswett): Use a more sophisticated TLP timer or never lose
+      // the last ack?
+      clock_->AdvanceTime(QuicTime::Delta::FromMilliseconds(100));
+      SendDataNow(&pending_transfers_.front());
+    } else if (ack_event.time_delta < send_event.time_delta) {
+      DVLOG(1) << "Handling ack of largest observed:"
+               << ack_event.transfer->sender->next_acked << ", advancing time:"
+               << ack_event.time_delta.ToMicroseconds() << "us";
+      // Ack data all the data up to ack time and lose any missing sequence
+      // numbers.
+      clock_->AdvanceTime(ack_event.time_delta);
+      HandlePendingAck(ack_event.transfer);
+    } else {
+      DVLOG(1) << "Sending, advancing time:"
+               << send_event.time_delta.ToMicroseconds() << "us";
+      clock_->AdvanceTime(send_event.time_delta);
+      SendDataNow(send_event.transfer);
+      bytes_sent += kPacketSize;
+    }
+  }
+}
+
+SendAlgorithmSimulator::PacketEvent SendAlgorithmSimulator::NextSendEvent() {
+  QuicTime::Delta next_send_time = QuicTime::Delta::Infinite();
+  Transfer* transfer = nullptr;
+  for (vector<Transfer>::iterator it = pending_transfers_.begin();
+       it != pending_transfers_.end(); ++it) {
+    // If we've already sent enough bytes, wait for them to be acked.
+    if (it->bytes_acked + it->bytes_in_flight >= it->num_bytes) {
+      continue;
+    }
+    // If the flow hasn't started, use the start time.
+    QuicTime::Delta transfer_send_time = it->start_time.Subtract(clock_->Now());
+    if (clock_->Now() >= it->start_time) {
+      transfer_send_time =
+          it->sender->send_algorithm->TimeUntilSend(
+              clock_->Now(), it->bytes_in_flight, HAS_RETRANSMITTABLE_DATA);
+    }
+    if (transfer_send_time < next_send_time) {
+      next_send_time = transfer_send_time;
+      transfer = &(*it);
+    }
+  }
+  DVLOG(1) << "NextSendTime returning delta(ms):"
+           << next_send_time.ToMilliseconds();
+  return PacketEvent(next_send_time, transfer);
+}
+
+// NextAck takes into account packet loss in both forward and reverse
+// direction, as well as correlated losses.  And it assumes the receiver acks
+// every other packet when there is no loss.
+SendAlgorithmSimulator::PacketEvent SendAlgorithmSimulator::NextAckEvent() {
+  if (sent_packets_.empty()) {
+    DVLOG(1) << "No outstanding packets to ack for any transfer.";
+    return PacketEvent(QuicTime::Delta::Infinite(), nullptr);
+  }
+
+  // For each connection, find the next acked packet.
+  QuicTime::Delta ack_time = QuicTime::Delta::Infinite();
+  Transfer* transfer = nullptr;
+  for (vector<Transfer>::iterator it = pending_transfers_.begin();
+       it != pending_transfers_.end(); ++it) {
+    QuicTime::Delta transfer_ack_time = FindNextAcked(&(*it));
+    if (transfer_ack_time < ack_time) {
+      ack_time = transfer_ack_time;
+      transfer = &(*it);
+    }
+  }
+
+  return PacketEvent(ack_time, transfer);
+}
+
+QuicTime::Delta SendAlgorithmSimulator::FindNextAcked(Transfer* transfer) {
+  Sender* sender = transfer->sender;
+  if (sender->next_acked == sender->last_acked) {
+    // Determine if the next ack is lost only once, to ensure determinism.
+    lose_next_ack_ =
+        reverse_loss_rate_ * kuint64max > simple_random_.RandUint64();
+  }
+
+  QuicPacketSequenceNumber next_acked = sender->last_acked;
+  QuicTime::Delta next_ack_delay =
+      FindNextAck(transfer, sender->last_acked, &next_acked);
+  if (lose_next_ack_) {
+    next_ack_delay = FindNextAck(transfer, next_acked, &next_acked);
+  }
+  sender->next_acked = next_acked;
+  return next_ack_delay;
+}
+
+QuicTime::Delta SendAlgorithmSimulator::FindNextAck(
+    const Transfer* transfer,
+    QuicPacketSequenceNumber last_acked,
+    QuicPacketSequenceNumber* next_acked) const {
+  *next_acked = last_acked;
+  QuicTime::Delta ack_delay = QuicTime::Delta::Infinite();
+  // Remove any packets that are simulated as lost.
+  for (list<SentPacket>::const_iterator it = sent_packets_.begin();
+       it != sent_packets_.end(); ++it) {
+    if (transfer != it->transfer) {
+      continue;
+    }
+    // Skip over any packets less than or equal to last_acked.
+    if (it->sequence_number <= last_acked) {
+      continue;
+    }
+    // Lost packets don't trigger an ack.
+    if (it->lost) {
+      continue;
+    }
+    DCHECK_LT(*next_acked, it->sequence_number);
+    // Consider a delayed ack for the current next_acked.
+    if (ack_delay < it->ack_time.Subtract(clock_->Now())) {
+      break;
+    }
+    *next_acked = it->sequence_number;
+    ack_delay = it->ack_time.Subtract(clock_->Now());
+    if (HasRecentLostPackets(transfer, *next_acked) ||
+        (*next_acked - last_acked) >= 2) {
+      break;
+    }
+    ack_delay = ack_delay.Add(delayed_ack_timer_);
+  }
+
+  DVLOG(1) << "FindNextAcked found next_acked_:"
+           << transfer->sender->next_acked
+           << " last_acked:" << transfer->sender->last_acked
+           << " ack_delay(ms):" << ack_delay.ToMilliseconds();
+  return ack_delay;
+}
+
+bool SendAlgorithmSimulator::HasRecentLostPackets(
+    const Transfer* transfer, QuicPacketSequenceNumber next_acked) const {
+  QuicPacketSequenceNumber last_packet = transfer->sender->last_acked;
+  for (list<SentPacket>::const_iterator it = sent_packets_.begin();
+       it != sent_packets_.end() && it->sequence_number < next_acked; ++it) {
+    if (transfer != it->transfer) {
+      continue;
+    }
+    // Lost packets don't trigger an ack.
+    if (it->lost) {
+      return true;
+    }
+    // Buffer dropped packets are skipped automatically, but still end up
+    // being lost and cause acks to be sent immediately.
+    if (it->sequence_number > last_packet + 1) {
+      return true;
+    }
+    last_packet = it->sequence_number;
+  }
+  return false;
+}
+
+void SendAlgorithmSimulator::HandlePendingAck(Transfer* transfer) {
+  Sender* sender  = transfer->sender;
+  DCHECK_LT(sender->last_acked, sender->next_acked);
+  SendAlgorithmInterface::CongestionVector acked_packets;
+  SendAlgorithmInterface::CongestionVector lost_packets;
+  DVLOG(1) << "Acking packets from:" << sender->last_acked
+             << " to " << sender->next_acked
+             << " bytes_in_flight:" << transfer->bytes_in_flight
+             << " Now():" << (clock_->Now().ToDebuggingValue() / 1000) << "ms";
+  // Some entries may be missing from the sent_packets_ array, if they were
+  // dropped due to buffer overruns.
+  SentPacket largest_observed;
+  list<SentPacket>::iterator it = sent_packets_.begin();
+  while (sender->last_acked < sender->next_acked) {
+    ++sender->last_acked;
+    TransmissionInfo info = TransmissionInfo();
+    info.bytes_sent = kPacketSize;
+    info.in_flight = true;
+    // Find the next SentPacket for this transfer.
+    while (it->transfer != transfer) {
+      DCHECK(it != sent_packets_.end());
+      ++it;
+    }
+    // If it's missing from the array, it's a loss.
+    if (it->sequence_number > sender->last_acked) {
+      DVLOG(1) << "Lost packet:" << sender->last_acked
+               << " dropped by buffer overflow.";
+      lost_packets.push_back(make_pair(sender->last_acked, info));
+      continue;
+    }
+    if (it->lost) {
+      lost_packets.push_back(make_pair(sender->last_acked, info));
+    } else {
+      acked_packets.push_back(make_pair(sender->last_acked, info));
+    }
+    // This packet has been acked or lost, remove it from sent_packets_.
+    largest_observed = *it;
+    sent_packets_.erase(it++);
+  }
+
+  DCHECK(largest_observed.ack_time.IsInitialized());
+  DVLOG(1) << "Updating RTT from send_time:"
+           << largest_observed.send_time.ToDebuggingValue() << " to ack_time:"
+           << largest_observed.ack_time.ToDebuggingValue();
+  QuicTime::Delta measured_rtt =
+      largest_observed.ack_time.Subtract(largest_observed.send_time);
+  DCHECK_GE(measured_rtt.ToMicroseconds(), rtt_.ToMicroseconds());
+  sender->rtt_stats->UpdateRtt(measured_rtt,
+                               QuicTime::Delta::Zero(),
+                               clock_->Now());
+  sender->send_algorithm->OnCongestionEvent(
+      true, transfer->bytes_in_flight, acked_packets, lost_packets);
+  DCHECK_LE(kPacketSize * (acked_packets.size() + lost_packets.size()),
+            transfer->bytes_in_flight);
+  transfer->bytes_in_flight -=
+      kPacketSize * (acked_packets.size() + lost_packets.size());
+
+  sender->RecordStats();
+  transfer->bytes_acked += acked_packets.size() * kPacketSize;
+  transfer->bytes_lost += lost_packets.size() * kPacketSize;
+  if (transfer->bytes_acked >= transfer->num_bytes) {
+    // Remove completed transfers and record transfer bandwidth.
+    QuicTime::Delta transfer_time =
+        clock_->Now().Subtract(transfer->start_time);
+    sender->last_transfer_loss_rate = static_cast<float>(transfer->bytes_lost) /
+        (transfer->bytes_lost + transfer->bytes_acked);
+    sender->last_transfer_bandwidth =
+        QuicBandwidth::FromBytesAndTimeDelta(transfer->num_bytes,
+                                             transfer_time);
+    DCHECK_GE(bandwidth_.ToBitsPerSecond(),
+              sender->last_transfer_bandwidth.ToBitsPerSecond());
+    for (vector<Transfer>::iterator it = pending_transfers_.begin();
+         it != pending_transfers_.end(); ++it) {
+      if (transfer == &(*it)) {
+        pending_transfers_.erase(it);
+        break;
+      }
+    }
+  }
+}
+
+void SendAlgorithmSimulator::SendDataNow(Transfer* transfer) {
+  Sender* sender  = transfer->sender;
+  ++sender->last_sent;
+  DVLOG(1) << "Sending packet:" << sender->last_sent
+           << " bytes_in_flight:" << transfer->bytes_in_flight
+           << " Now():" << (clock_->Now().ToDebuggingValue() / 1000) << "ms";
+  sender->send_algorithm->OnPacketSent(
+      clock_->Now(), transfer->bytes_in_flight,
+      sender->last_sent, kPacketSize, HAS_RETRANSMITTABLE_DATA);
+  // Lose the packet immediately if the buffer is full.
+  if (sent_packets_.size() * kPacketSize < buffer_size_) {
+    // TODO(ianswett): This buffer simulation is an approximation.
+    // An ack time of zero means loss.
+    bool packet_lost =
+        forward_loss_rate_ * kuint64max > simple_random_.RandUint64();
+    // Handle correlated loss.
+    if (!sent_packets_.empty() && sent_packets_.back().lost &&
+        loss_correlation_ * kuint64max > simple_random_.RandUint64()) {
+      packet_lost = true;
+    }
+    DVLOG(1) << "losing packet:" << sender->last_sent
+             << " due to random loss.";
+
+    // If the number of bytes in flight are less than the bdp, there's
+    // no buffering delay.  Bytes lost from the buffer are not counted.
+    QuicByteCount bdp = bandwidth_.ToBytesPerPeriod(rtt_);
+    QuicTime ack_time = clock_->Now().Add(rtt_);
+    if (kPacketSize > bdp) {
+      ack_time = ack_time.Add(bandwidth_.TransferTime(kPacketSize - bdp));
+    }
+    QuicTime queue_ack_time = sent_packets_.empty() ? QuicTime::Zero() :
+        sent_packets_.back().ack_time.Add(bandwidth_.TransferTime(kPacketSize));
+    ack_time = QuicTime::Max(ack_time, queue_ack_time);
+    sent_packets_.push_back(SentPacket(
+        sender->last_sent, clock_->Now(), ack_time, packet_lost, transfer));
+  } else {
+    DVLOG(1) << "losing packet:" << sender->last_sent
+             << " because the buffer was full.";
+  }
+  transfer->bytes_in_flight += kPacketSize;
+}
+
+// Advance the time by |delta| without sending anything.
+void SendAlgorithmSimulator::AdvanceTime(QuicTime::Delta delta) {
+  clock_->AdvanceTime(delta);
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/send_algorithm_simulator.h b/net/quic/congestion_control/send_algorithm_simulator.h
new file mode 100644
index 0000000..3e61d88
--- /dev/null
+++ b/net/quic/congestion_control/send_algorithm_simulator.h
@@ -0,0 +1,231 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A test only class to enable simulations of send algorithms.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_SIMULATOR_H_
+#define NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_SIMULATOR_H_
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPrintf;
+
+namespace net {
+
+class SendAlgorithmSimulator {
+ public:
+  struct Sender {
+    Sender(SendAlgorithmInterface* send_algorithm, RttStats* rtt_stats);
+
+    void RecordStats() {
+      QuicByteCount cwnd = send_algorithm->GetCongestionWindow();
+      max_cwnd = std::max(max_cwnd, cwnd);
+      min_cwnd = std::min(min_cwnd, cwnd);
+      if (last_cwnd > cwnd) {
+        max_cwnd_drop = std::max(max_cwnd_drop, last_cwnd - cwnd);
+      }
+      last_cwnd = cwnd;
+    }
+
+    std::string DebugString() {
+      return StringPrintf("observed goodput(bytes/s):%" PRId64
+                          " loss rate:%f"
+                          " cwnd:%" PRIu64
+                          " max_cwnd:%" PRIu64 " min_cwnd:%" PRIu64
+                          " max_cwnd_drop:%" PRIu64,
+                          last_transfer_bandwidth.ToBytesPerSecond(),
+                          last_transfer_loss_rate,
+                          send_algorithm->GetCongestionWindow(),
+                          max_cwnd, min_cwnd, max_cwnd_drop);
+    }
+
+    SendAlgorithmInterface* send_algorithm;
+    RttStats* rtt_stats;
+
+    // Last sequence number the sender sent.
+    QuicPacketSequenceNumber last_sent;
+    // Last packet sequence number acked.
+    QuicPacketSequenceNumber last_acked;
+    // Packet sequence number to ack up to.
+    QuicPacketSequenceNumber next_acked;
+
+    // Stats collected for understanding the congestion control.
+    QuicByteCount max_cwnd;
+    QuicByteCount min_cwnd;
+    QuicByteCount max_cwnd_drop;
+    QuicByteCount last_cwnd;
+
+    QuicBandwidth last_transfer_bandwidth;
+    float last_transfer_loss_rate;
+  };
+
+  struct Transfer {
+    Transfer(Sender* sender, QuicByteCount num_bytes, QuicTime start_time)
+        : sender(sender),
+          num_bytes(num_bytes),
+          bytes_acked(0),
+          bytes_lost(0),
+          bytes_in_flight(0),
+          start_time(start_time) {}
+
+    Sender* sender;
+    QuicByteCount num_bytes;
+    QuicByteCount bytes_acked;
+    QuicByteCount bytes_lost;
+    QuicByteCount bytes_in_flight;
+    QuicTime start_time;
+  };
+
+  struct SentPacket {
+    SentPacket()
+        : sequence_number(0),
+          send_time(QuicTime::Zero()),
+          ack_time(QuicTime::Zero()),
+          lost(false),
+          transfer(nullptr) {}
+    SentPacket(QuicPacketSequenceNumber sequence_number,
+               QuicTime send_time,
+               QuicTime ack_time,
+               bool lost,
+               Transfer* transfer)
+        : sequence_number(sequence_number),
+          send_time(send_time),
+          ack_time(ack_time),
+          lost(lost),
+          transfer(transfer) {}
+
+    QuicPacketSequenceNumber sequence_number;
+    QuicTime send_time;
+    QuicTime ack_time;
+    bool lost;
+    Transfer* transfer;
+  };
+
+  // |rtt_stats| should be the same RttStats used by the |send_algorithm|.
+  SendAlgorithmSimulator(MockClock* clock_,
+                         QuicBandwidth bandwidth,
+                         QuicTime::Delta rtt);
+  ~SendAlgorithmSimulator();
+
+  void set_bandwidth(QuicBandwidth bandwidth) {
+    bandwidth_ = bandwidth;
+  }
+
+  void set_forward_loss_rate(float loss_rate) {
+    DCHECK_LT(loss_rate, 1.0f);
+    forward_loss_rate_ = loss_rate;
+  }
+
+  void set_reverse_loss_rate(float loss_rate) {
+    DCHECK_LT(loss_rate, 1.0f);
+    reverse_loss_rate_ = loss_rate;
+  }
+
+  void set_loss_correlation(float loss_correlation) {
+    DCHECK_LT(loss_correlation, 1.0f);
+    loss_correlation_ = loss_correlation;
+  }
+
+  void set_buffer_size(size_t buffer_size_bytes) {
+    buffer_size_ = buffer_size_bytes;
+  }
+
+  void set_delayed_ack_timer(QuicTime::Delta delayed_ack_timer) {
+    delayed_ack_timer_ = delayed_ack_timer;
+  }
+
+  // Advance the time by |delta| without sending anything.
+  void AdvanceTime(QuicTime::Delta delta);
+
+  // Adds a pending sender.  The send will run when TransferBytes is called.
+  // Adding two transfers with the same sender is unsupported.
+  void AddTransfer(Sender* sender, size_t num_bytes);
+
+  // Adds a pending sending to start at the specified time.
+  void AddTransfer(Sender* sender, size_t num_bytes, QuicTime start_time);
+
+  // Convenience method to transfer all bytes.
+  void TransferBytes();
+
+  // Transfers bytes through the connection until |max_bytes| are reached,
+  // |max_time| is reached, or all senders have finished sending.  If max_bytes
+  // is 0, it does not apply, and if |max_time| is Zero, no time limit applies.
+  void TransferBytes(QuicByteCount max_bytes, QuicTime::Delta max_time);
+
+ private:
+  // A pending packet event, either a send or an ack.
+  struct PacketEvent {
+    PacketEvent(QuicTime::Delta time_delta, Transfer* transfer)
+        : time_delta(time_delta),
+          transfer(transfer) {}
+
+    QuicTime::Delta time_delta;
+    Transfer* transfer;
+  };
+
+  // NextSendTime returns the next time any of the pending transfers send,
+  // and populates transfer if the send time is not infinite.
+  PacketEvent NextSendEvent();
+
+  // NextAckTime takes into account packet loss in both forward and reverse
+  // direction, as well as delayed ack behavior.
+  PacketEvent NextAckEvent();
+
+  // Sets the next acked.
+  QuicTime::Delta FindNextAcked(Transfer* transfer);
+
+  // Sets the |next_acked| packet for the |transfer| starting at the specified
+  // |last_acked|.  Returns QuicTime::Delta::Infinite and doesn't set
+  // |next_acked| if there is no ack after |last_acked|.
+  QuicTime::Delta FindNextAck(const Transfer* transfer,
+                              QuicPacketSequenceNumber last_acked,
+                              QuicPacketSequenceNumber* next_acked) const;
+
+  // Returns true if any of the packets |transfer| is waiting for less than
+  // next_acked have been lost.
+  bool HasRecentLostPackets(const Transfer* transfer,
+                            QuicPacketSequenceNumber next_acked) const;
+
+  // Process all the acks that should have arrived by the current time, and
+  // lose any packets that are missing.  Returns the number of bytes acked.
+  void HandlePendingAck(Transfer* transfer);
+
+  void SendDataNow(Transfer* transfer);
+
+  // List of all pending transfers waiting to use the connection.
+  std::vector<Transfer> pending_transfers_;
+
+  MockClock* clock_;
+  // Whether the next ack should be lost.
+  bool lose_next_ack_;
+  // The times acks are expected, assuming acks are not lost and every packet
+  // is acked.
+  std::list<SentPacket> sent_packets_;
+
+  test::SimpleRandom simple_random_;
+  float forward_loss_rate_;  // Loss rate on the forward path.
+  float reverse_loss_rate_;  // Loss rate on the reverse path.
+  float loss_correlation_;   // Likelihood the subsequent packet is lost.
+  QuicBandwidth bandwidth_;
+  QuicTime::Delta rtt_;
+  size_t buffer_size_;       // In bytes.
+  QuicTime::Delta delayed_ack_timer_;
+
+  DISALLOW_COPY_AND_ASSIGN(SendAlgorithmSimulator);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_SIMULATOR_H_
diff --git a/net/quic/congestion_control/tcp_cubic_sender.cc b/net/quic/congestion_control/tcp_cubic_sender.cc
new file mode 100644
index 0000000..0aae5fd
--- /dev/null
+++ b/net/quic/congestion_control/tcp_cubic_sender.cc
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+
+#include <algorithm>
+
+#include "base/metrics/histogram.h"
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+using std::max;
+using std::min;
+
+namespace net {
+
+namespace {
+// Constants based on TCP defaults.
+// The minimum cwnd based on RFC 3782 (TCP NewReno) for cwnd reductions on a
+// fast retransmission.  The cwnd after a timeout is still 1.
+const QuicTcpCongestionWindow kMinimumCongestionWindow = 2;
+const QuicByteCount kMaxSegmentSize = kDefaultTCPMSS;
+const int64 kInitialCongestionWindow = 10;
+const int kMaxBurstLength = 3;
+};  // namespace
+
+TcpCubicSender::TcpCubicSender(
+    const QuicClock* clock,
+    const RttStats* rtt_stats,
+    bool reno,
+    QuicTcpCongestionWindow max_tcp_congestion_window,
+    QuicConnectionStats* stats)
+    : hybrid_slow_start_(clock),
+      cubic_(clock, stats),
+      rtt_stats_(rtt_stats),
+      stats_(stats),
+      reno_(reno),
+      num_connections_(2),
+      congestion_window_count_(0),
+      receive_window_(kDefaultSocketReceiveBuffer),
+      prr_out_(0),
+      prr_delivered_(0),
+      ack_count_since_loss_(0),
+      bytes_in_flight_before_loss_(0),
+      largest_sent_sequence_number_(0),
+      largest_acked_sequence_number_(0),
+      largest_sent_at_last_cutback_(0),
+      congestion_window_(kInitialCongestionWindow),
+      previous_congestion_window_(0),
+      slowstart_threshold_(max_tcp_congestion_window),
+      previous_slowstart_threshold_(0),
+      last_cutback_exited_slowstart_(false),
+      max_tcp_congestion_window_(max_tcp_congestion_window) {
+}
+
+TcpCubicSender::~TcpCubicSender() {
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.FinalTcpCwnd", congestion_window_);
+}
+
+void TcpCubicSender::SetFromConfig(const QuicConfig& config, bool is_server) {
+  if (is_server) {
+    if (config.HasReceivedConnectionOptions() &&
+        ContainsQuicTag(config.ReceivedConnectionOptions(), kIW10)) {
+      // Initial window experiment.  Ignore the initial congestion
+      // window suggested by the client and use the default ICWND of
+      // 10 instead.
+      congestion_window_ = kInitialCongestionWindow;
+    } else if (config.HasReceivedInitialCongestionWindow()) {
+      // Set the initial window size.
+      congestion_window_ = min(kMaxInitialWindow,
+                               config.ReceivedInitialCongestionWindow());
+    }
+  }
+  if (config.HasReceivedSocketReceiveBuffer()) {
+    // Set the initial socket receive buffer size in bytes.
+    receive_window_ = config.ReceivedSocketReceiveBuffer();
+  }
+}
+
+void TcpCubicSender::SetNumEmulatedConnections(int num_connections) {
+  num_connections_ = max(1, num_connections);
+  cubic_.SetNumConnections(num_connections_);
+}
+
+void TcpCubicSender::OnIncomingQuicCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& feedback,
+    QuicTime feedback_receive_time) {
+  if (feedback.type == kTCP) {
+    receive_window_ = feedback.tcp.receive_window;
+  }
+}
+
+void TcpCubicSender::OnCongestionEvent(
+    bool rtt_updated,
+    QuicByteCount bytes_in_flight,
+    const CongestionVector& acked_packets,
+    const CongestionVector& lost_packets) {
+  if (rtt_updated && InSlowStart() &&
+      hybrid_slow_start_.ShouldExitSlowStart(rtt_stats_->latest_rtt(),
+                                             rtt_stats_->min_rtt(),
+                                             congestion_window_)) {
+    slowstart_threshold_ = congestion_window_;
+  }
+  for (CongestionVector::const_iterator it = lost_packets.begin();
+       it != lost_packets.end(); ++it) {
+    OnPacketLost(it->first, bytes_in_flight);
+  }
+  for (CongestionVector::const_iterator it = acked_packets.begin();
+       it != acked_packets.end(); ++it) {
+    OnPacketAcked(it->first, it->second.bytes_sent, bytes_in_flight);
+  }
+}
+
+void TcpCubicSender::OnPacketAcked(
+    QuicPacketSequenceNumber acked_sequence_number,
+    QuicByteCount acked_bytes,
+    QuicByteCount bytes_in_flight) {
+  largest_acked_sequence_number_ = max(acked_sequence_number,
+                                       largest_acked_sequence_number_);
+  if (InRecovery()) {
+    PrrOnPacketAcked(acked_bytes);
+    return;
+  }
+  MaybeIncreaseCwnd(acked_sequence_number, bytes_in_flight);
+  // TODO(ianswett): Should this even be called when not in slow start?
+  hybrid_slow_start_.OnPacketAcked(acked_sequence_number, InSlowStart());
+}
+
+void TcpCubicSender::OnPacketLost(QuicPacketSequenceNumber sequence_number,
+                                  QuicByteCount bytes_in_flight) {
+  // TCP NewReno (RFC6582) says that once a loss occurs, any losses in packets
+  // already sent should be treated as a single loss event, since it's expected.
+  if (sequence_number <= largest_sent_at_last_cutback_) {
+    if (last_cutback_exited_slowstart_) {
+      ++stats_->slowstart_packets_lost;
+    }
+    DVLOG(1) << "Ignoring loss for largest_missing:" << sequence_number
+             << " because it was sent prior to the last CWND cutback.";
+    return;
+  }
+  ++stats_->tcp_loss_events;
+  last_cutback_exited_slowstart_ = InSlowStart();
+  if (InSlowStart()) {
+    ++stats_->slowstart_packets_lost;
+  }
+  PrrOnPacketLost(bytes_in_flight);
+
+  if (reno_) {
+    congestion_window_ = congestion_window_ >> 1;
+  } else {
+    congestion_window_ =
+        cubic_.CongestionWindowAfterPacketLoss(congestion_window_);
+  }
+  slowstart_threshold_ = congestion_window_;
+  // Enforce TCP's minimum congestion window of 2*MSS.
+  if (congestion_window_ < kMinimumCongestionWindow) {
+    congestion_window_ = kMinimumCongestionWindow;
+  }
+  largest_sent_at_last_cutback_ = largest_sent_sequence_number_;
+  // reset packet count from congestion avoidance mode. We start
+  // counting again when we're out of recovery.
+  congestion_window_count_ = 0;
+  DVLOG(1) << "Incoming loss; congestion window: " << congestion_window_
+           << " slowstart threshold: " << slowstart_threshold_;
+}
+
+bool TcpCubicSender::OnPacketSent(QuicTime /*sent_time*/,
+                                  QuicByteCount /*bytes_in_flight*/,
+                                  QuicPacketSequenceNumber sequence_number,
+                                  QuicByteCount bytes,
+                                  HasRetransmittableData is_retransmittable) {
+  // Only update bytes_in_flight_ for data packets.
+  if (is_retransmittable != HAS_RETRANSMITTABLE_DATA) {
+    return false;
+  }
+
+  prr_out_ += bytes;
+  DCHECK_LT(largest_sent_sequence_number_, sequence_number);
+  largest_sent_sequence_number_ = sequence_number;
+  hybrid_slow_start_.OnPacketSent(sequence_number);
+  return true;
+}
+
+QuicTime::Delta TcpCubicSender::TimeUntilSend(
+    QuicTime /* now */,
+    QuicByteCount bytes_in_flight,
+    HasRetransmittableData has_retransmittable_data) const {
+  if (has_retransmittable_data == NO_RETRANSMITTABLE_DATA) {
+    // For TCP we can always send an ACK immediately.
+    return QuicTime::Delta::Zero();
+  }
+  if (InRecovery()) {
+    return PrrTimeUntilSend(bytes_in_flight);
+  }
+  if (SendWindow() > bytes_in_flight) {
+    return QuicTime::Delta::Zero();
+  }
+  return QuicTime::Delta::Infinite();
+}
+
+QuicByteCount TcpCubicSender::SendWindow() const {
+  // What's the current send window in bytes.
+  return min(receive_window_, GetCongestionWindow());
+}
+
+QuicBandwidth TcpCubicSender::BandwidthEstimate() const {
+  return QuicBandwidth::FromBytesAndTimeDelta(GetCongestionWindow(),
+                                              rtt_stats_->SmoothedRtt());
+}
+
+bool TcpCubicSender::HasReliableBandwidthEstimate() const {
+  return !InSlowStart() && !InRecovery();
+}
+
+QuicTime::Delta TcpCubicSender::RetransmissionDelay() const {
+  if (!rtt_stats_->HasUpdates()) {
+    return QuicTime::Delta::Zero();
+  }
+  return QuicTime::Delta::FromMicroseconds(
+      rtt_stats_->SmoothedRtt().ToMicroseconds() +
+      4 * rtt_stats_->mean_deviation().ToMicroseconds());
+}
+
+QuicByteCount TcpCubicSender::GetCongestionWindow() const {
+  return congestion_window_ * kMaxSegmentSize;
+}
+
+bool TcpCubicSender::InSlowStart() const {
+  return congestion_window_ < slowstart_threshold_;
+}
+
+QuicByteCount TcpCubicSender::GetSlowStartThreshold() const {
+  return slowstart_threshold_ * kMaxSegmentSize;
+}
+
+bool TcpCubicSender::IsCwndLimited(QuicByteCount bytes_in_flight) const {
+  const QuicByteCount congestion_window_bytes = congestion_window_ *
+      kMaxSegmentSize;
+  if (bytes_in_flight >= congestion_window_bytes) {
+    return true;
+  }
+  const QuicByteCount max_burst = kMaxBurstLength * kMaxSegmentSize;
+  const QuicByteCount available_bytes =
+      congestion_window_bytes - bytes_in_flight;
+  const bool slow_start_limited = InSlowStart() &&
+      bytes_in_flight >  congestion_window_bytes / 2;
+  return slow_start_limited || available_bytes <= max_burst;
+}
+
+bool TcpCubicSender::InRecovery() const {
+  return largest_acked_sequence_number_ <= largest_sent_at_last_cutback_ &&
+      largest_acked_sequence_number_ != 0;
+}
+
+// Called when we receive an ack. Normal TCP tracks how many packets one ack
+// represents, but quic has a separate ack for each packet.
+void TcpCubicSender::MaybeIncreaseCwnd(
+    QuicPacketSequenceNumber acked_sequence_number,
+    QuicByteCount bytes_in_flight) {
+  LOG_IF(DFATAL, InRecovery()) << "Never increase the CWND during recovery.";
+  if (!IsCwndLimited(bytes_in_flight)) {
+    // We don't update the congestion window unless we are close to using the
+    // window we have available.
+    return;
+  }
+  if (InSlowStart()) {
+    // congestion_window_cnt is the number of acks since last change of snd_cwnd
+    if (congestion_window_ < max_tcp_congestion_window_) {
+      // TCP slow start, exponential growth, increase by one for each ACK.
+      ++congestion_window_;
+    }
+    DVLOG(1) << "Slow start; congestion window: " << congestion_window_
+             << " slowstart threshold: " << slowstart_threshold_;
+    return;
+  }
+  if (congestion_window_ >= max_tcp_congestion_window_) {
+    return;
+  }
+  // Congestion avoidance
+  if (reno_) {
+    // Classic Reno congestion avoidance.
+    ++congestion_window_count_;
+    // Divide by num_connections to smoothly increase the CWND at a faster
+    // rate than conventional Reno.
+    if (congestion_window_count_ * num_connections_ >= congestion_window_) {
+      ++congestion_window_;
+      congestion_window_count_ = 0;
+    }
+
+    DVLOG(1) << "Reno; congestion window: " << congestion_window_
+             << " slowstart threshold: " << slowstart_threshold_
+             << " congestion window count: " << congestion_window_count_;
+  } else {
+    congestion_window_ = min(max_tcp_congestion_window_,
+                             cubic_.CongestionWindowAfterAck(
+                                 congestion_window_, rtt_stats_->min_rtt()));
+    DVLOG(1) << "Cubic; congestion window: " << congestion_window_
+             << " slowstart threshold: " << slowstart_threshold_;
+  }
+}
+
+void TcpCubicSender::OnRetransmissionTimeout(bool packets_retransmitted) {
+  largest_sent_at_last_cutback_ = 0;
+  if (!packets_retransmitted) {
+    return;
+  }
+  cubic_.Reset();
+  hybrid_slow_start_.Restart();
+  previous_slowstart_threshold_ = slowstart_threshold_;
+  slowstart_threshold_ = congestion_window_ / 2;
+  previous_congestion_window_ = congestion_window_;
+  congestion_window_ = kMinimumCongestionWindow;
+}
+
+void TcpCubicSender::RevertRetransmissionTimeout() {
+  if (previous_congestion_window_ == 0) {
+    LOG(DFATAL) << "No previous congestion window to revert to.";
+    return;
+  }
+  congestion_window_ = previous_congestion_window_;
+  slowstart_threshold_ = previous_slowstart_threshold_;
+  previous_congestion_window_ = 0;
+}
+
+void TcpCubicSender::PrrOnPacketLost(QuicByteCount bytes_in_flight) {
+  prr_out_ = 0;
+  bytes_in_flight_before_loss_ = bytes_in_flight;
+  prr_delivered_ = 0;
+  ack_count_since_loss_ = 0;
+}
+
+void TcpCubicSender::PrrOnPacketAcked(QuicByteCount acked_bytes) {
+  prr_delivered_ += acked_bytes;
+  ++ack_count_since_loss_;
+}
+
+QuicTime::Delta TcpCubicSender::PrrTimeUntilSend(
+    QuicByteCount bytes_in_flight) const {
+  DCHECK(InRecovery());
+  // Return QuicTime::Zero In order to ensure limited transmit always works.
+  if (prr_out_ == 0 || bytes_in_flight < kMaxSegmentSize) {
+    return QuicTime::Delta::Zero();
+  }
+  if (SendWindow() > bytes_in_flight) {
+    // During PRR-SSRB, limit outgoing packets to 1 extra MSS per ack, instead
+    // of sending the entire available window. This prevents burst retransmits
+    // when more packets are lost than the CWND reduction.
+    //   limit = MAX(prr_delivered - prr_out, DeliveredData) + MSS
+    if (prr_delivered_ + ack_count_since_loss_ * kMaxSegmentSize <= prr_out_) {
+      return QuicTime::Delta::Infinite();
+    }
+    return QuicTime::Delta::Zero();
+  }
+  // Implement Proportional Rate Reduction (RFC6937)
+  // Checks a simplified version of the PRR formula that doesn't use division:
+  // AvailableSendWindow =
+  //   CEIL(prr_delivered * ssthresh / BytesInFlightAtLoss) - prr_sent
+  if (prr_delivered_ * slowstart_threshold_ * kMaxSegmentSize >
+          prr_out_ * bytes_in_flight_before_loss_) {
+    return QuicTime::Delta::Zero();
+  }
+  return QuicTime::Delta::Infinite();
+}
+
+CongestionControlType TcpCubicSender::GetCongestionControlType() const {
+  return reno_ ? kReno : kCubic;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/tcp_cubic_sender.h b/net/quic/congestion_control/tcp_cubic_sender.h
new file mode 100644
index 0000000..8851ac4
--- /dev/null
+++ b/net/quic/congestion_control/tcp_cubic_sender.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// TCP cubic send side congestion algorithm, emulates the behavior of
+// TCP cubic.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/cubic.h"
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class RttStats;
+
+namespace test {
+class TcpCubicSenderPeer;
+}  // namespace test
+
+class NET_EXPORT_PRIVATE TcpCubicSender : public SendAlgorithmInterface {
+ public:
+  // Reno option and max_tcp_congestion_window are provided for testing.
+  TcpCubicSender(const QuicClock* clock,
+                 const RttStats* rtt_stats,
+                 bool reno,
+                 QuicTcpCongestionWindow max_tcp_congestion_window,
+                 QuicConnectionStats* stats);
+  virtual ~TcpCubicSender();
+
+  // Start implementation of SendAlgorithmInterface.
+  virtual void SetFromConfig(const QuicConfig& config, bool is_server) OVERRIDE;
+  virtual void SetNumEmulatedConnections(int num_connections) OVERRIDE;
+  virtual void OnIncomingQuicCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& feedback,
+      QuicTime feedback_receive_time) OVERRIDE;
+  virtual void OnCongestionEvent(bool rtt_updated,
+                                 QuicByteCount bytes_in_flight,
+                                 const CongestionVector& acked_packets,
+                                 const CongestionVector& lost_packets) OVERRIDE;
+  virtual bool OnPacketSent(QuicTime sent_time,
+                            QuicByteCount bytes_in_flight,
+                            QuicPacketSequenceNumber sequence_number,
+                            QuicByteCount bytes,
+                            HasRetransmittableData is_retransmittable) OVERRIDE;
+  virtual void OnRetransmissionTimeout(bool packets_retransmitted) OVERRIDE;
+  virtual void RevertRetransmissionTimeout() OVERRIDE;
+  virtual QuicTime::Delta TimeUntilSend(
+      QuicTime now,
+      QuicByteCount bytes_in_flight,
+      HasRetransmittableData has_retransmittable_data) const OVERRIDE;
+  virtual QuicBandwidth BandwidthEstimate() const OVERRIDE;
+  virtual bool HasReliableBandwidthEstimate() const OVERRIDE;
+  virtual QuicTime::Delta RetransmissionDelay() const OVERRIDE;
+  virtual QuicByteCount GetCongestionWindow() const OVERRIDE;
+  virtual bool InSlowStart() const OVERRIDE;
+  virtual bool InRecovery() const OVERRIDE;
+  virtual QuicByteCount GetSlowStartThreshold() const OVERRIDE;
+  virtual CongestionControlType GetCongestionControlType() const OVERRIDE;
+  // End implementation of SendAlgorithmInterface.
+
+ private:
+  friend class test::TcpCubicSenderPeer;
+
+  // TODO(ianswett): Remove these and migrate to OnCongestionEvent.
+  void OnPacketAcked(QuicPacketSequenceNumber acked_sequence_number,
+                     QuicByteCount acked_bytes,
+                     QuicByteCount bytes_in_flight);
+  void OnPacketLost(QuicPacketSequenceNumber largest_loss,
+                    QuicByteCount bytes_in_flight);
+
+  QuicByteCount SendWindow() const;
+  void MaybeIncreaseCwnd(QuicPacketSequenceNumber acked_sequence_number,
+                         QuicByteCount bytes_in_flight);
+  bool IsCwndLimited(QuicByteCount bytes_in_flight) const;
+  // Methods for isolating PRR from the rest of TCP Cubic.
+  void PrrOnPacketLost(QuicByteCount bytes_in_flight);
+  void PrrOnPacketAcked(QuicByteCount acked_bytes);
+  QuicTime::Delta PrrTimeUntilSend(QuicByteCount bytes_in_flight) const;
+
+
+  HybridSlowStart hybrid_slow_start_;
+  Cubic cubic_;
+  const RttStats* rtt_stats_;
+  QuicConnectionStats* stats_;
+
+  // If true, Reno congestion control is used instead of Cubic.
+  const bool reno_;
+
+  // Number of connections to simulate.
+  int num_connections_;
+
+  // ACK counter for the Reno implementation.
+  int64 congestion_window_count_;
+
+  // Receiver side advertised window.
+  QuicByteCount receive_window_;
+
+  // Bytes sent and acked since the last loss event.  Used for PRR.
+  QuicByteCount prr_out_;
+  QuicByteCount prr_delivered_;
+  size_t ack_count_since_loss_;
+
+  // The congestion window before the last loss event.
+  QuicByteCount bytes_in_flight_before_loss_;
+
+  // Track the largest packet that has been sent.
+  QuicPacketSequenceNumber largest_sent_sequence_number_;
+
+  // Track the largest packet that has been acked.
+  QuicPacketSequenceNumber largest_acked_sequence_number_;
+
+  // Track the largest sequence number outstanding when a CWND cutback occurs.
+  QuicPacketSequenceNumber largest_sent_at_last_cutback_;
+
+  // Congestion window in packets.
+  QuicTcpCongestionWindow congestion_window_;
+
+  // Congestion window before the last loss event or RTO.
+  QuicByteCount previous_congestion_window_;
+
+  // Slow start congestion window in packets, aka ssthresh.
+  QuicTcpCongestionWindow slowstart_threshold_;
+
+  // Slow start threshold before the last loss event or RTO.
+  QuicTcpCongestionWindow previous_slowstart_threshold_;
+
+  // Whether the last loss event caused us to exit slowstart.
+  // Used for stats collection of slowstart_packets_lost
+  bool last_cutback_exited_slowstart_;
+
+  // Maximum number of outstanding packets for tcp.
+  QuicTcpCongestionWindow max_tcp_congestion_window_;
+
+  DISALLOW_COPY_AND_ASSIGN(TcpCubicSender);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
diff --git a/net/quic/congestion_control/tcp_cubic_sender_test.cc b/net/quic/congestion_control/tcp_cubic_sender_test.cc
new file mode 100644
index 0000000..851c1a1
--- /dev/null
+++ b/net/quic/congestion_control/tcp_cubic_sender_test.cc
@@ -0,0 +1,699 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_config_peer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::make_pair;
+using std::min;
+
+namespace net {
+namespace test {
+
+const int64 kInitialCongestionWindow = 10;
+const uint32 kDefaultWindowTCP = kInitialCongestionWindow * kDefaultTCPMSS;
+
+// TODO(ianswett): Remove 10000 once b/10075719 is fixed.
+const QuicTcpCongestionWindow kDefaultMaxCongestionWindowTCP = 10000;
+
+class TcpCubicSenderPeer : public TcpCubicSender {
+ public:
+  TcpCubicSenderPeer(const QuicClock* clock,
+                     bool reno,
+                     QuicTcpCongestionWindow max_tcp_congestion_window)
+      : TcpCubicSender(
+            clock, &rtt_stats_, reno, max_tcp_congestion_window, &stats_) {
+  }
+
+  QuicTcpCongestionWindow congestion_window() {
+    return congestion_window_;
+  }
+
+  QuicTcpCongestionWindow slowstart_threshold() {
+    return slowstart_threshold_;
+  }
+
+  const HybridSlowStart& hybrid_slow_start() const {
+    return hybrid_slow_start_;
+  }
+
+  RttStats rtt_stats_;
+  QuicConnectionStats stats_;
+
+  using TcpCubicSender::SendWindow;
+};
+
+class TcpCubicSenderTest : public ::testing::Test {
+ protected:
+  TcpCubicSenderTest()
+      : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+        sender_(new TcpCubicSenderPeer(&clock_, true,
+                                       kDefaultMaxCongestionWindowTCP)),
+        receiver_(new TcpReceiver()),
+        sequence_number_(1),
+        acked_sequence_number_(0),
+        bytes_in_flight_(0) {
+    standard_packet_.bytes_sent = kDefaultTCPMSS;
+  }
+
+  int SendAvailableSendWindow() {
+    // Send as long as TimeUntilSend returns Zero.
+    int packets_sent = 0;
+    bool can_send = sender_->TimeUntilSend(
+        clock_.Now(), bytes_in_flight_, HAS_RETRANSMITTABLE_DATA).IsZero();
+    while (can_send) {
+      sender_->OnPacketSent(clock_.Now(), bytes_in_flight_, sequence_number_++,
+                            kDefaultTCPMSS, HAS_RETRANSMITTABLE_DATA);
+      ++packets_sent;
+      bytes_in_flight_ += kDefaultTCPMSS;
+      can_send = sender_->TimeUntilSend(
+          clock_.Now(), bytes_in_flight_, HAS_RETRANSMITTABLE_DATA).IsZero();
+    }
+    return packets_sent;
+  }
+
+  // Normal is that TCP acks every other segment.
+  void AckNPackets(int n) {
+    sender_->rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(60),
+                                  QuicTime::Delta::Zero(),
+                                  clock_.Now());
+    SendAlgorithmInterface::CongestionVector acked_packets;
+    SendAlgorithmInterface::CongestionVector lost_packets;
+    for (int i = 0; i < n; ++i) {
+      ++acked_sequence_number_;
+      acked_packets.push_back(
+          make_pair(acked_sequence_number_, standard_packet_));
+    }
+    sender_->OnCongestionEvent(
+        true, bytes_in_flight_, acked_packets, lost_packets);
+    bytes_in_flight_ -= n * kDefaultTCPMSS;
+    clock_.AdvanceTime(one_ms_);
+  }
+
+  void LoseNPackets(int n) {
+    SendAlgorithmInterface::CongestionVector acked_packets;
+    SendAlgorithmInterface::CongestionVector lost_packets;
+    for (int i = 0; i < n; ++i) {
+      ++acked_sequence_number_;
+      lost_packets.push_back(
+          make_pair(acked_sequence_number_, standard_packet_));
+    }
+    sender_->OnCongestionEvent(
+        false, bytes_in_flight_, acked_packets, lost_packets);
+    bytes_in_flight_ -= n * kDefaultTCPMSS;
+  }
+
+  // Does not increment acked_sequence_number_.
+  void LosePacket(QuicPacketSequenceNumber sequence_number) {
+    SendAlgorithmInterface::CongestionVector acked_packets;
+    SendAlgorithmInterface::CongestionVector lost_packets;
+    lost_packets.push_back(
+        make_pair(sequence_number, standard_packet_));
+    sender_->OnCongestionEvent(
+        false, bytes_in_flight_, acked_packets, lost_packets);
+    bytes_in_flight_ -= kDefaultTCPMSS;
+  }
+
+  const QuicTime::Delta one_ms_;
+  MockClock clock_;
+  scoped_ptr<TcpCubicSenderPeer> sender_;
+  scoped_ptr<TcpReceiver> receiver_;
+  QuicPacketSequenceNumber sequence_number_;
+  QuicPacketSequenceNumber acked_sequence_number_;
+  QuicByteCount bytes_in_flight_;
+  TransmissionInfo standard_packet_;
+};
+
+TEST_F(TcpCubicSenderTest, SimpleSender) {
+  // At startup make sure we are at the default.
+  EXPECT_EQ(kDefaultWindowTCP, sender_->GetCongestionWindow());
+  // At startup make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+                                     0,
+                                     HAS_RETRANSMITTABLE_DATA).IsZero());
+  // Make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+                                     0,
+                                     HAS_RETRANSMITTABLE_DATA).IsZero());
+  // And that window is un-affected.
+  EXPECT_EQ(kDefaultWindowTCP, sender_->GetCongestionWindow());
+
+  // Fill the send window with data, then verify that we can't send.
+  SendAvailableSendWindow();
+  EXPECT_FALSE(sender_->TimeUntilSend(clock_.Now(),
+                                      sender_->GetCongestionWindow(),
+                                      HAS_RETRANSMITTABLE_DATA).IsZero());
+}
+
+TEST_F(TcpCubicSenderTest, ApplicationLimitedSlowStart) {
+  // Send exactly 10 packets and ensure the CWND ends at 14 packets.
+  const int kNumberOfAcks = 5;
+  // At startup make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+      0,
+      HAS_RETRANSMITTABLE_DATA).IsZero());
+  // Make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+                                     0,
+                                     HAS_RETRANSMITTABLE_DATA).IsZero());
+
+  SendAvailableSendWindow();
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    AckNPackets(2);
+  }
+  QuicByteCount bytes_to_send = sender_->SendWindow();
+  // It's expected 2 acks will arrive when the bytes_in_flight are greater than
+  // half the CWND.
+  EXPECT_EQ(kDefaultWindowTCP + kDefaultTCPMSS * 2 * 2,
+            bytes_to_send);
+}
+
+TEST_F(TcpCubicSenderTest, ExponentialSlowStart) {
+  const int kNumberOfAcks = 20;
+  // At startup make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+      0,
+      HAS_RETRANSMITTABLE_DATA).IsZero());
+  // Make sure we can send.
+  EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+                                     0,
+                                     HAS_RETRANSMITTABLE_DATA).IsZero());
+
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  QuicByteCount bytes_to_send = sender_->SendWindow();
+  EXPECT_EQ(kDefaultWindowTCP + kDefaultTCPMSS * 2 * kNumberOfAcks,
+            bytes_to_send);
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartAckTrain) {
+  sender_->SetNumEmulatedConnections(1);
+  EXPECT_EQ(kDefaultMaxCongestionWindowTCP * kDefaultTCPMSS,
+            sender_->GetSlowStartThreshold());
+
+  // Make sure that we fall out of slow start when we send ACK train longer
+  // than half the RTT, in this test case 30ms, which is more than 30 calls to
+  // Ack2Packets in one round.
+  // Since we start at 10 packet first round will be 5 second round 10 etc
+  // Hence we should pass 30 at 65 = 5 + 10 + 20 + 30
+  const int kNumberOfAcks = 65;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  QuicByteCount expected_send_window =
+      kDefaultWindowTCP + (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // We should now have fallen out of slow start.
+  // Testing Reno phase.
+  // We should need 140(65*2+10) ACK:ed packets before increasing window by
+  // one.
+  for (int i = 0; i < 69; ++i) {
+    SendAvailableSendWindow();
+    AckNPackets(2);
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+  SendAvailableSendWindow();
+  AckNPackets(2);
+  QuicByteCount expected_ss_tresh = expected_send_window;
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  EXPECT_EQ(expected_ss_tresh, sender_->GetSlowStartThreshold());
+  EXPECT_EQ(140u, sender_->slowstart_threshold());
+
+  // Now RTO and ensure slow start gets reset.
+  EXPECT_TRUE(sender_->hybrid_slow_start().started());
+  sender_->OnRetransmissionTimeout(true);
+  EXPECT_FALSE(sender_->hybrid_slow_start().started());
+  EXPECT_EQ(2 * kDefaultTCPMSS, sender_->GetCongestionWindow());
+  EXPECT_EQ(expected_send_window / 2 / kDefaultTCPMSS,
+            sender_->slowstart_threshold());
+
+  // Now revert the RTO and ensure the CWND and slowstart threshold revert.
+  sender_->RevertRetransmissionTimeout();
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  EXPECT_EQ(140u, sender_->slowstart_threshold());
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartPacketLoss) {
+  sender_->SetNumEmulatedConnections(1);
+  const int kNumberOfAcks = 10;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  SendAvailableSendWindow();
+  QuicByteCount expected_send_window = kDefaultWindowTCP +
+      (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Lose a packet to exit slow start.
+  LoseNPackets(1);
+
+  // We should now have fallen out of slow start.
+  // We expect window to be cut in half by Reno.
+  expected_send_window /= 2;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Testing Reno phase.
+  // We need to ack half of the pending packet before we can send again.
+  size_t number_of_packets_in_window = expected_send_window / kDefaultTCPMSS;
+  AckNPackets(number_of_packets_in_window);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // We need to ack every packet in the window before we exit recovery.
+  for (size_t i = 0; i < number_of_packets_in_window; ++i) {
+    AckNPackets(1);
+    SendAvailableSendWindow();
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  // We need to ack another window before we increase CWND by 1.
+  for (size_t i = 0; i < number_of_packets_in_window - 2; ++i) {
+    AckNPackets(1);
+    SendAvailableSendWindow();
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  AckNPackets(1);
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Now RTO and ensure slow start gets reset.
+  EXPECT_TRUE(sender_->hybrid_slow_start().started());
+  sender_->OnRetransmissionTimeout(true);
+  EXPECT_FALSE(sender_->hybrid_slow_start().started());
+}
+
+TEST_F(TcpCubicSenderTest, NoPRRWhenLessThanOnePacketInFlight) {
+  SendAvailableSendWindow();
+  LoseNPackets(kInitialCongestionWindow - 1);
+  AckNPackets(1);
+  // PRR will allow 2 packets for every ack during recovery.
+  EXPECT_EQ(2, SendAvailableSendWindow());
+  // Simulate abandoning all packets by supplying a bytes_in_flight of 0.
+  // PRR should now allow a packet to be sent, even though prr's state
+  // variables believe it has sent enough packets.
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            sender_->TimeUntilSend(clock_.Now(), 0, HAS_RETRANSMITTABLE_DATA));
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartPacketLossPRR) {
+  sender_->SetNumEmulatedConnections(1);
+  // Test based on the first example in RFC6937.
+  // Ack 10 packets in 5 acks to raise the CWND to 20, as in the example.
+  const int kNumberOfAcks = 5;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  SendAvailableSendWindow();
+  QuicByteCount expected_send_window = kDefaultWindowTCP +
+      (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  LoseNPackets(1);
+
+  // We should now have fallen out of slow start.
+  // We expect window to be cut in half by Reno.
+  expected_send_window /= 2;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Testing TCP proportional rate reduction.
+  // We should send one packet for every two received acks over the remaining
+  // 18 outstanding packets.
+  size_t number_of_packets_in_window = expected_send_window / kDefaultTCPMSS;
+  // The number of packets before we exit recovery is the original CWND minus
+  // the packet that has been lost and the one which triggered the loss.
+  size_t remaining_packets_in_recovery = number_of_packets_in_window * 2 - 1;
+  for (size_t i = 0; i < remaining_packets_in_recovery - 1; i += 2) {
+    AckNPackets(2);
+    EXPECT_TRUE(sender_->TimeUntilSend(
+        clock_.Now(), bytes_in_flight_, HAS_RETRANSMITTABLE_DATA).IsZero());
+    EXPECT_EQ(1, SendAvailableSendWindow());
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  // We need to ack another window before we increase CWND by 1.
+  for (size_t i = 0; i < number_of_packets_in_window; ++i) {
+    AckNPackets(1);
+    EXPECT_EQ(1, SendAvailableSendWindow());
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  AckNPackets(1);
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartBurstPacketLossPRR) {
+  sender_->SetNumEmulatedConnections(1);
+  // Test based on the second example in RFC6937, though we also implement
+  // forward acknowledgements, so the first two incoming acks will trigger
+  // PRR immediately.
+  // Ack 10 packets in 5 acks to raise the CWND to 20, as in the example.
+  const int kNumberOfAcks = 5;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  SendAvailableSendWindow();
+  QuicByteCount expected_send_window = kDefaultWindowTCP +
+      (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Ack a packet with a 15 packet gap, losing 13 of them due to FACK.
+  LoseNPackets(13);
+  // Immediately after the loss, ensure at least one packet can be sent.
+  // Losses without subsequent acks can occur with timer based loss detection.
+  EXPECT_TRUE(sender_->TimeUntilSend(
+      clock_.Now(), bytes_in_flight_, HAS_RETRANSMITTABLE_DATA).IsZero());
+  AckNPackets(1);
+
+  // We should now have fallen out of slow start.
+  // We expect window to be cut in half by Reno.
+  expected_send_window /= 2;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Only 2 packets should be allowed to be sent, per PRR-SSRB
+  EXPECT_EQ(2, SendAvailableSendWindow());
+
+  // Ack the next packet, which triggers another loss.
+  LoseNPackets(1);
+  AckNPackets(1);
+
+  // Send 2 packets to simulate PRR-SSRB.
+  EXPECT_EQ(2, SendAvailableSendWindow());
+
+  // Ack the next packet, which triggers another loss.
+  LoseNPackets(1);
+  AckNPackets(1);
+
+  // Send 2 packets to simulate PRR-SSRB.
+  EXPECT_EQ(2, SendAvailableSendWindow());
+
+  AckNPackets(1);
+  EXPECT_EQ(2, SendAvailableSendWindow());
+
+  AckNPackets(1);
+  EXPECT_EQ(2, SendAvailableSendWindow());
+
+  // The window should not have changed.
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Exit recovery and return to sending at the new rate.
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    AckNPackets(1);
+    EXPECT_EQ(1, SendAvailableSendWindow());
+  }
+}
+
+TEST_F(TcpCubicSenderTest, RTOCongestionWindowAndRevert) {
+  EXPECT_EQ(kDefaultWindowTCP, sender_->SendWindow());
+  EXPECT_EQ(10000u, sender_->slowstart_threshold());
+
+  // Expect the window to decrease to the minimum once the RTO fires
+  // and slow start threshold to be set to 1/2 of the CWND.
+  sender_->OnRetransmissionTimeout(true);
+  EXPECT_EQ(2 * kDefaultTCPMSS, sender_->SendWindow());
+  EXPECT_EQ(5u, sender_->slowstart_threshold());
+
+  // Now repair the RTO and ensure the slowstart threshold reverts.
+  sender_->RevertRetransmissionTimeout();
+  EXPECT_EQ(kDefaultWindowTCP, sender_->SendWindow());
+  EXPECT_EQ(10000u, sender_->slowstart_threshold());
+}
+
+TEST_F(TcpCubicSenderTest, RTOCongestionWindowNoRetransmission) {
+  EXPECT_EQ(kDefaultWindowTCP, sender_->SendWindow());
+
+  // Expect the window to remain unchanged if the RTO fires but no
+  // packets are retransmitted.
+  sender_->OnRetransmissionTimeout(false);
+  EXPECT_EQ(kDefaultWindowTCP, sender_->SendWindow());
+}
+
+TEST_F(TcpCubicSenderTest, RetransmissionDelay) {
+  const int64 kRttMs = 10;
+  const int64 kDeviationMs = 3;
+  EXPECT_EQ(QuicTime::Delta::Zero(), sender_->RetransmissionDelay());
+
+  sender_->rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(kRttMs),
+                                QuicTime::Delta::Zero(), clock_.Now());
+
+  // Initial value is to set the median deviation to half of the initial
+  // rtt, the median in then multiplied by a factor of 4 and finally the
+  // smoothed rtt is added which is the initial rtt.
+  QuicTime::Delta expected_delay =
+      QuicTime::Delta::FromMilliseconds(kRttMs + kRttMs / 2 * 4);
+  EXPECT_EQ(expected_delay, sender_->RetransmissionDelay());
+
+  for (int i = 0; i < 100; ++i) {
+    // Run to make sure that we converge.
+    sender_->rtt_stats_.UpdateRtt(
+        QuicTime::Delta::FromMilliseconds(kRttMs + kDeviationMs),
+        QuicTime::Delta::Zero(), clock_.Now());
+    sender_->rtt_stats_.UpdateRtt(
+        QuicTime::Delta::FromMilliseconds(kRttMs - kDeviationMs),
+        QuicTime::Delta::Zero(), clock_.Now());
+  }
+  expected_delay = QuicTime::Delta::FromMilliseconds(kRttMs + kDeviationMs * 4);
+
+  EXPECT_NEAR(kRttMs, sender_->rtt_stats_.SmoothedRtt().ToMilliseconds(), 1);
+  EXPECT_NEAR(expected_delay.ToMilliseconds(),
+              sender_->RetransmissionDelay().ToMilliseconds(),
+              1);
+  EXPECT_EQ(static_cast<int64>(
+                sender_->GetCongestionWindow() * kNumMicrosPerSecond /
+                sender_->rtt_stats_.SmoothedRtt().ToMicroseconds()),
+            sender_->BandwidthEstimate().ToBytesPerSecond());
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartMaxSendWindow) {
+  const QuicTcpCongestionWindow kMaxCongestionWindowTCP = 50;
+  const int kNumberOfAcks = 100;
+  sender_.reset(
+      new TcpCubicSenderPeer(&clock_, false, kMaxCongestionWindowTCP));
+
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  QuicByteCount expected_send_window =
+      kMaxCongestionWindowTCP * kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, TcpRenoMaxCongestionWindow) {
+  const QuicTcpCongestionWindow kMaxCongestionWindowTCP = 50;
+  const int kNumberOfAcks = 1000;
+  sender_.reset(
+      new TcpCubicSenderPeer(&clock_, true, kMaxCongestionWindowTCP));
+
+  SendAvailableSendWindow();
+  AckNPackets(2);
+  // Make sure we fall out of slow start.
+  LoseNPackets(1);
+
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+
+  QuicByteCount expected_send_window =
+      kMaxCongestionWindowTCP * kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, TcpCubicMaxCongestionWindow) {
+  const QuicTcpCongestionWindow kMaxCongestionWindowTCP = 50;
+  // Set to 10000 to compensate for small cubic alpha.
+  const int kNumberOfAcks = 10000;
+
+  sender_.reset(
+      new TcpCubicSenderPeer(&clock_, false, kMaxCongestionWindowTCP));
+
+  SendAvailableSendWindow();
+  AckNPackets(2);
+  // Make sure we fall out of slow start.
+  LoseNPackets(1);
+
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+
+  QuicByteCount expected_send_window =
+      kMaxCongestionWindowTCP * kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, MultipleLossesInOneWindow) {
+  SendAvailableSendWindow();
+  const QuicByteCount initial_window = sender_->GetCongestionWindow();
+  LosePacket(acked_sequence_number_ + 1);
+  const QuicByteCount post_loss_window = sender_->GetCongestionWindow();
+  EXPECT_GT(initial_window, post_loss_window);
+  LosePacket(acked_sequence_number_ + 3);
+  EXPECT_EQ(post_loss_window, sender_->GetCongestionWindow());
+  LosePacket(sequence_number_ - 1);
+  EXPECT_EQ(post_loss_window, sender_->GetCongestionWindow());
+
+  // Lose a later packet and ensure the window decreases.
+  LosePacket(sequence_number_);
+  EXPECT_GT(post_loss_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, DontTrackAckPackets) {
+  // Send a packet with no retransmittable data, and ensure it's not tracked.
+  EXPECT_FALSE(sender_->OnPacketSent(clock_.Now(), bytes_in_flight_,
+                                     sequence_number_++, kDefaultTCPMSS,
+                                     NO_RETRANSMITTABLE_DATA));
+
+  // Send a data packet with retransmittable data, and ensure it is tracked.
+  EXPECT_TRUE(sender_->OnPacketSent(clock_.Now(), bytes_in_flight_,
+                                    sequence_number_++, kDefaultTCPMSS,
+                                    HAS_RETRANSMITTABLE_DATA));
+}
+
+TEST_F(TcpCubicSenderTest, ConfigureMaxInitialWindow) {
+  QuicTcpCongestionWindow congestion_window = sender_->congestion_window();
+  QuicConfig config;
+  QuicConfigPeer::SetReceivedInitialWindow(&config, 2 * congestion_window);
+
+  sender_->SetFromConfig(config, true);
+  EXPECT_EQ(2 * congestion_window, sender_->congestion_window());
+
+  // Verify that kCOPT: kIW10 forces the congestion window to the
+  // default of 10 regardless of ReceivedInitialWindow.
+  QuicTagVector options;
+  options.push_back(kIW10);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  sender_->SetFromConfig(config, true);
+  EXPECT_EQ(congestion_window, sender_->congestion_window());
+}
+
+TEST_F(TcpCubicSenderTest, 2ConnectionCongestionAvoidanceAtEndOfRecovery) {
+  sender_->SetNumEmulatedConnections(2);
+  // Ack 10 packets in 5 acks to raise the CWND to 20.
+  const int kNumberOfAcks = 5;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  SendAvailableSendWindow();
+  QuicByteCount expected_send_window = kDefaultWindowTCP +
+      (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  LoseNPackets(1);
+
+  // We should now have fallen out of slow start, and window should be cut in
+  // half by Reno. New cwnd should be 10.
+  expected_send_window /= 2;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // No congestion window growth should occur in recovery phase, i.e., until the
+  // currently outstanding 20 packets are acked.
+  for (int i = 0; i < 10; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    EXPECT_TRUE(sender_->InRecovery());
+    AckNPackets(2);
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+  EXPECT_FALSE(sender_->InRecovery());
+
+  // Out of recovery now. Congestion window should not grow for half an RTT.
+  SendAvailableSendWindow();
+  AckNPackets(2);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // Next ack will be the 5th and should increase congestion window by 1MSS.
+  AckNPackets(2);
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  for (int i = 0; i < 2; ++i) {
+    SendAvailableSendWindow();
+    AckNPackets(2);
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  // Next ack should cause congestion window to grow by 1MSS.
+  AckNPackets(2);
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, 1ConnectionCongestionAvoidanceAtEndOfRecovery) {
+  sender_->SetNumEmulatedConnections(1);
+  // Ack 10 packets in 5 acks to raise the CWND to 20.
+  const int kNumberOfAcks = 5;
+  for (int i = 0; i < kNumberOfAcks; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+  }
+  SendAvailableSendWindow();
+  QuicByteCount expected_send_window = kDefaultWindowTCP +
+      (kDefaultTCPMSS * 2 * kNumberOfAcks);
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  LoseNPackets(1);
+
+  // We should now have fallen out of slow start, and window should be cut in
+  // half by Reno. New cwnd should be 10.
+  expected_send_window /= 2;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+
+  // No congestion window growth should occur in recovery phase, i.e.,
+  // until the currently outstanding 20 packets are acked.
+  for (int i = 0; i < 10; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    EXPECT_TRUE(sender_->InRecovery());
+    AckNPackets(2);
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+  EXPECT_FALSE(sender_->InRecovery());
+
+  // Out of recovery now. Congestion window should not grow during RTT.
+  for (int i = 0; i < 4; ++i) {
+    // Send our full send window.
+    SendAvailableSendWindow();
+    AckNPackets(2);
+    EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+  }
+
+  // Next ack should cause congestion window to grow by 1MSS.
+  AckNPackets(2);
+  expected_send_window += kDefaultTCPMSS;
+  EXPECT_EQ(expected_send_window, sender_->GetCongestionWindow());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/tcp_loss_algorithm.cc b/net/quic/congestion_control/tcp_loss_algorithm.cc
new file mode 100644
index 0000000..557681e
--- /dev/null
+++ b/net/quic/congestion_control/tcp_loss_algorithm.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/tcp_loss_algorithm.h"
+
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace {
+
+// TCP retransmits after 3 nacks.
+static const size_t kNumberOfNacksBeforeRetransmission = 3;
+
+// How many RTTs the algorithm waits before determining a packet is lost due
+// to early retransmission.
+static const double kEarlyRetransmitLossDelayMultiplier = 1.25;
+
+}
+
+TCPLossAlgorithm::TCPLossAlgorithm()
+    : loss_detection_timeout_(QuicTime::Zero()) { }
+
+LossDetectionType TCPLossAlgorithm::GetLossDetectionType() const {
+  return kNack;
+}
+
+// Uses nack counts to decide when packets are lost.
+SequenceNumberSet TCPLossAlgorithm::DetectLostPackets(
+    const QuicUnackedPacketMap& unacked_packets,
+    const QuicTime& time,
+    QuicPacketSequenceNumber largest_observed,
+    const RttStats& rtt_stats) {
+  SequenceNumberSet lost_packets;
+  loss_detection_timeout_ = QuicTime::Zero();
+  QuicTime::Delta loss_delay =
+      rtt_stats.SmoothedRtt().Multiply(kEarlyRetransmitLossDelayMultiplier);
+  QuicPacketSequenceNumber sequence_number = unacked_packets.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets.begin();
+       it != unacked_packets.end() && sequence_number <= largest_observed;
+       ++it, ++sequence_number) {
+    if (!it->in_flight) {
+      continue;
+    }
+
+    LOG_IF(DFATAL, it->nack_count == 0)
+        << "All packets less than largest observed should have been nacked.";
+    if (it->nack_count >= kNumberOfNacksBeforeRetransmission) {
+      lost_packets.insert(sequence_number);
+      continue;
+    }
+
+    // Only early retransmit(RFC5827) when the last packet gets acked and
+    // there are retransmittable packets in flight.
+    // This also implements a timer-protected variant of FACK.
+    if (it->retransmittable_frames &&
+        unacked_packets.largest_sent_packet() == largest_observed) {
+      // Early retransmit marks the packet as lost once 1.25RTTs have passed
+      // since the packet was sent and otherwise sets an alarm.
+      if (time >= it->sent_time.Add(loss_delay)) {
+        lost_packets.insert(sequence_number);
+      } else {
+        // Set the timeout for the earliest retransmittable packet where early
+        // retransmit applies.
+        loss_detection_timeout_ = it->sent_time.Add(loss_delay);
+        break;
+      }
+    }
+  }
+
+  return lost_packets;
+}
+
+QuicTime TCPLossAlgorithm::GetLossTimeout() const {
+  return loss_detection_timeout_;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/tcp_loss_algorithm.h b/net/quic/congestion_control/tcp_loss_algorithm.h
new file mode 100644
index 0000000..5f0bcfb
--- /dev/null
+++ b/net/quic/congestion_control/tcp_loss_algorithm.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TCP_LOSS_ALGORITHM_H_
+#define NET_QUIC_CONGESTION_CONTROL_TCP_LOSS_ALGORITHM_H_
+
+#include <algorithm>
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/quic_unacked_packet_map.h"
+
+namespace net {
+
+// Class which implement's TCP's approach of detecting loss when 3 nacks have
+// been received for a packet.  Also implements TCP's early retransmit(RFC5827).
+class NET_EXPORT_PRIVATE TCPLossAlgorithm : public LossDetectionInterface {
+ public:
+  TCPLossAlgorithm();
+  virtual ~TCPLossAlgorithm() {}
+
+  virtual LossDetectionType GetLossDetectionType() const OVERRIDE;
+
+  // Uses nack counts to decide when packets are lost.
+  virtual SequenceNumberSet DetectLostPackets(
+      const QuicUnackedPacketMap& unacked_packets,
+      const QuicTime& time,
+      QuicPacketSequenceNumber largest_observed,
+      const RttStats& rtt_stats) OVERRIDE;
+
+  // Returns a non-zero value when the early retransmit timer is active.
+  virtual QuicTime GetLossTimeout() const OVERRIDE;
+
+ private:
+  QuicTime loss_detection_timeout_;
+
+  DISALLOW_COPY_AND_ASSIGN(TCPLossAlgorithm);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_TCP_LOSS_ALGORITHM_H_
diff --git a/net/quic/congestion_control/tcp_loss_algorithm_test.cc b/net/quic/congestion_control/tcp_loss_algorithm_test.cc
new file mode 100644
index 0000000..988f427
--- /dev/null
+++ b/net/quic/congestion_control/tcp_loss_algorithm_test.cc
@@ -0,0 +1,183 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/congestion_control/tcp_loss_algorithm.h"
+#include "net/quic/quic_unacked_packet_map.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class TcpLossAlgorithmTest : public ::testing::Test {
+ protected:
+  TcpLossAlgorithmTest()
+      : unacked_packets_() {
+    rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(100),
+                         QuicTime::Delta::Zero(),
+                         clock_.Now());
+  }
+
+  void SendDataPacket(QuicPacketSequenceNumber sequence_number) {
+    SerializedPacket packet(sequence_number, PACKET_1BYTE_SEQUENCE_NUMBER,
+                            nullptr, 0, new RetransmittableFrames());
+    unacked_packets_.AddPacket(packet);
+    unacked_packets_.SetSent(sequence_number, clock_.Now(), 1000, true);
+  }
+
+  void VerifyLosses(QuicPacketSequenceNumber largest_observed,
+                    QuicPacketSequenceNumber* losses_expected,
+                    size_t num_losses) {
+    SequenceNumberSet lost_packets =
+        loss_algorithm_.DetectLostPackets(
+            unacked_packets_, clock_.Now(), largest_observed, rtt_stats_);
+    EXPECT_EQ(num_losses, lost_packets.size());
+    for (size_t i = 0; i < num_losses; ++i) {
+      EXPECT_TRUE(ContainsKey(lost_packets, losses_expected[i]));
+    }
+  }
+
+  QuicUnackedPacketMap unacked_packets_;
+  TCPLossAlgorithm loss_algorithm_;
+  RttStats rtt_stats_;
+  MockClock clock_;
+};
+
+TEST_F(TcpLossAlgorithmTest, NackRetransmit1Packet) {
+  const size_t kNumSentPackets = 5;
+  // Transmit 5 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+  // No loss on one ack.
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.NackPacket(1, 1);
+  VerifyLosses(2, nullptr, 0);
+  // No loss on two acks.
+  unacked_packets_.RemoveFromInFlight(3);
+  unacked_packets_.NackPacket(1, 2);
+  VerifyLosses(3, nullptr, 0);
+  // Loss on three acks.
+  unacked_packets_.RemoveFromInFlight(4);
+  unacked_packets_.NackPacket(1, 3);
+  QuicPacketSequenceNumber lost[] = { 1 };
+  VerifyLosses(4, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+// A stretch ack is an ack that covers more than 1 packet of previously
+// unacknowledged data.
+TEST_F(TcpLossAlgorithmTest, NackRetransmit1PacketWith1StretchAck) {
+  const size_t kNumSentPackets = 10;
+  // Transmit 10 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+
+  // Nack the first packet 3 times in a single StretchAck.
+  unacked_packets_.NackPacket(1, 3);
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.RemoveFromInFlight(3);
+  unacked_packets_.RemoveFromInFlight(4);
+  QuicPacketSequenceNumber lost[] = { 1 };
+  VerifyLosses(4, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+// Ack a packet 3 packets ahead, causing a retransmit.
+TEST_F(TcpLossAlgorithmTest, NackRetransmit1PacketSingleAck) {
+  const size_t kNumSentPackets = 10;
+  // Transmit 10 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+
+  // Nack the first packet 3 times in an AckFrame with three missing packets.
+  unacked_packets_.NackPacket(1, 3);
+  unacked_packets_.NackPacket(2, 2);
+  unacked_packets_.NackPacket(3, 1);
+  unacked_packets_.RemoveFromInFlight(4);
+  QuicPacketSequenceNumber lost[] = { 1 };
+  VerifyLosses(4, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(TcpLossAlgorithmTest, EarlyRetransmit1Packet) {
+  const size_t kNumSentPackets = 2;
+  // Transmit 2 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+  // Early retransmit when the final packet gets acked and the first is nacked.
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.NackPacket(1, 1);
+  VerifyLosses(2, nullptr, 0);
+  EXPECT_EQ(clock_.Now().Add(rtt_stats_.SmoothedRtt().Multiply(1.25)),
+            loss_algorithm_.GetLossTimeout());
+
+  clock_.AdvanceTime(rtt_stats_.latest_rtt().Multiply(1.25));
+  QuicPacketSequenceNumber lost[] = { 1 };
+  VerifyLosses(2, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(TcpLossAlgorithmTest, EarlyRetransmitAllPackets) {
+  const size_t kNumSentPackets = 5;
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+    // Advance the time 1/4 RTT between 3 and 4.
+    if (i == 3) {
+      clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.25));
+    }
+  }
+
+  // Early retransmit when the final packet gets acked and 1.25 RTTs have
+  // elapsed since the packets were sent.
+  unacked_packets_.RemoveFromInFlight(kNumSentPackets);
+  // This simulates a single ack following multiple missing packets with FACK.
+  for (size_t i = 1; i < kNumSentPackets; ++i) {
+    unacked_packets_.NackPacket(i, kNumSentPackets - i);
+  }
+  QuicPacketSequenceNumber lost[] = { 1, 2 };
+  VerifyLosses(kNumSentPackets, lost, arraysize(lost));
+  // The time has already advanced 1/4 an RTT, so ensure the timeout is set
+  // 1.25 RTTs after the earliest pending packet(3), not the last(4).
+  EXPECT_EQ(clock_.Now().Add(rtt_stats_.SmoothedRtt()),
+            loss_algorithm_.GetLossTimeout());
+
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt());
+  QuicPacketSequenceNumber lost2[] = { 1, 2, 3 };
+  VerifyLosses(kNumSentPackets, lost2, arraysize(lost2));
+  EXPECT_EQ(clock_.Now().Add(rtt_stats_.SmoothedRtt().Multiply(0.25)),
+            loss_algorithm_.GetLossTimeout());
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.25));
+  QuicPacketSequenceNumber lost3[] = { 1, 2, 3, 4 };
+  VerifyLosses(kNumSentPackets, lost3, arraysize(lost3));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(TcpLossAlgorithmTest, DontEarlyRetransmitNeuteredPacket) {
+  const size_t kNumSentPackets = 2;
+  // Transmit 2 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+  // Neuter packet 1.
+  unacked_packets_.RemoveRetransmittability(1);
+
+  // Early retransmit when the final packet gets acked and the first is nacked.
+  unacked_packets_.IncreaseLargestObserved(2);
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.NackPacket(1, 1);
+  VerifyLosses(2, nullptr, 0);
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/tcp_receiver.cc b/net/quic/congestion_control/tcp_receiver.cc
new file mode 100644
index 0000000..a8c5489
--- /dev/null
+++ b/net/quic/congestion_control/tcp_receiver.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+
+namespace net {
+
+// Originally 64K bytes, but increased it to 256K to support higher bitrates.
+// static
+const QuicByteCount TcpReceiver::kReceiveWindowTCP = 256000;
+
+TcpReceiver::TcpReceiver()
+    : receive_window_(kReceiveWindowTCP) {
+}
+
+bool TcpReceiver::GenerateCongestionFeedback(
+    QuicCongestionFeedbackFrame* feedback) {
+  feedback->type = kTCP;
+  feedback->tcp.receive_window = receive_window_;
+  return true;
+}
+
+void TcpReceiver::RecordIncomingPacket(QuicByteCount bytes,
+                                       QuicPacketSequenceNumber sequence_number,
+                                       QuicTime timestamp) {
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/tcp_receiver.h b/net/quic/congestion_control/tcp_receiver.h
new file mode 100644
index 0000000..a5e5b1b
--- /dev/null
+++ b/net/quic/congestion_control/tcp_receiver.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// TCP receiver side congestion algorithm, emulates the behaviour of TCP.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
+#define NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE TcpReceiver : public ReceiveAlgorithmInterface {
+ public:
+  TcpReceiver();
+
+  // Size of the (currently fixed) receive window.
+  static const QuicByteCount kReceiveWindowTCP;
+
+  // Start implementation of SendAlgorithmInterface.
+  virtual bool GenerateCongestionFeedback(
+      QuicCongestionFeedbackFrame* feedback) OVERRIDE;
+
+  virtual void RecordIncomingPacket(QuicByteCount bytes,
+                                    QuicPacketSequenceNumber sequence_number,
+                                    QuicTime timestamp) OVERRIDE;
+
+ private:
+  QuicByteCount receive_window_;
+
+  DISALLOW_COPY_AND_ASSIGN(TcpReceiver);
+};
+
+}  // namespace net
+#endif  // NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
diff --git a/net/quic/congestion_control/tcp_receiver_test.cc b/net/quic/congestion_control/tcp_receiver_test.cc
new file mode 100644
index 0000000..e074fdf
--- /dev/null
+++ b/net/quic/congestion_control/tcp_receiver_test.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicTcpReceiverTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    receiver_.reset(new TcpReceiver());
+  }
+  scoped_ptr<TcpReceiver> receiver_;
+};
+
+TEST_F(QuicTcpReceiverTest, SimpleReceiver) {
+  QuicCongestionFeedbackFrame feedback;
+  QuicTime timestamp(QuicTime::Zero());
+  receiver_->RecordIncomingPacket(1, 1, timestamp);
+  ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+  EXPECT_EQ(kTCP, feedback.type);
+  EXPECT_EQ(256000u, feedback.tcp.receive_window);
+  receiver_->RecordIncomingPacket(1, 2, timestamp);
+  ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+  EXPECT_EQ(kTCP, feedback.type);
+  EXPECT_EQ(256000u, feedback.tcp.receive_window);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/congestion_control/time_loss_algorithm.cc b/net/quic/congestion_control/time_loss_algorithm.cc
new file mode 100644
index 0000000..3a7b42d
--- /dev/null
+++ b/net/quic/congestion_control/time_loss_algorithm.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/congestion_control/time_loss_algorithm.h"
+
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace {
+
+// The minimum delay before a packet will be considered lost,
+// regardless of SRTT.  Half of the minimum TLP, since the loss algorithm only
+// triggers when a nack has been receieved for the packet.
+static const size_t kMinLossDelayMs = 5;
+
+// How many RTTs the algorithm waits before determining a packet is lost.
+static const double kLossDelayMultiplier = 1.25;
+
+}  // namespace
+
+TimeLossAlgorithm::TimeLossAlgorithm()
+    : loss_detection_timeout_(QuicTime::Zero()) { }
+
+LossDetectionType TimeLossAlgorithm::GetLossDetectionType() const {
+  return kTime;
+}
+
+SequenceNumberSet TimeLossAlgorithm::DetectLostPackets(
+    const QuicUnackedPacketMap& unacked_packets,
+    const QuicTime& time,
+    QuicPacketSequenceNumber largest_observed,
+    const RttStats& rtt_stats) {
+  SequenceNumberSet lost_packets;
+  loss_detection_timeout_ = QuicTime::Zero();
+  QuicTime::Delta loss_delay = QuicTime::Delta::Max(
+      QuicTime::Delta::FromMilliseconds(kMinLossDelayMs),
+      QuicTime::Delta::Max(rtt_stats.SmoothedRtt(), rtt_stats.latest_rtt())
+          .Multiply(kLossDelayMultiplier));
+
+  QuicPacketSequenceNumber sequence_number = unacked_packets.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets.begin();
+       it != unacked_packets.end() && sequence_number <= largest_observed;
+       ++it, ++sequence_number) {
+    if (!it->in_flight) {
+      continue;
+    }
+    LOG_IF(DFATAL, it->nack_count == 0)
+        << "All packets less than largest observed should have been nacked.";
+
+    // Packets are sent in order, so break when we haven't waited long enough
+    // to lose any more packets and leave the loss_time_ set for the timeout.
+    QuicTime when_lost = it->sent_time.Add(loss_delay);
+    if (time < when_lost) {
+      loss_detection_timeout_ = when_lost;
+      break;
+    }
+    lost_packets.insert(sequence_number);
+  }
+
+  return lost_packets;
+}
+
+// loss_time_ is updated in DetectLostPackets, which must be called every time
+// an ack is received or the timeout expires.
+QuicTime TimeLossAlgorithm::GetLossTimeout() const {
+  return loss_detection_timeout_;
+}
+
+}  // namespace net
diff --git a/net/quic/congestion_control/time_loss_algorithm.h b/net/quic/congestion_control/time_loss_algorithm.h
new file mode 100644
index 0000000..ae37e1e
--- /dev/null
+++ b/net/quic/congestion_control/time_loss_algorithm.h
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TIME_LOSS_ALGORITHM_H_
+#define NET_QUIC_CONGESTION_CONTROL_TIME_LOSS_ALGORITHM_H_
+
+#include <algorithm>
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/quic_unacked_packet_map.h"
+
+namespace net {
+
+// A loss detection algorithm which avoids spurious losses and retransmissions
+// by waiting 1.25 RTTs after a packet was sent instead of nack count.
+class NET_EXPORT_PRIVATE TimeLossAlgorithm : public LossDetectionInterface {
+ public:
+  TimeLossAlgorithm();
+  virtual ~TimeLossAlgorithm() {}
+
+  virtual LossDetectionType GetLossDetectionType() const OVERRIDE;
+
+  // Declares pending packets less than the largest observed lost when it has
+  // been 1.25 RTT since they were sent.  Packets larger than the largest
+  // observed are retransmitted via TLP.
+  virtual SequenceNumberSet DetectLostPackets(
+      const QuicUnackedPacketMap& unacked_packets,
+      const QuicTime& time,
+      QuicPacketSequenceNumber largest_observed,
+      const RttStats& rtt_stats) OVERRIDE;
+
+  // Returns the time the next packet will be lost, or zero if there
+  // are no nacked pending packets outstanding.
+  // TODO(ianswett): Ideally the RTT variance and the RTT would be used to
+  // determine the time a packet is considered lost.
+  // TODO(ianswett): Consider using Max(1.25 * srtt, 1.125 * last_rtt).
+  virtual QuicTime GetLossTimeout() const OVERRIDE;
+
+ private:
+  QuicTime loss_detection_timeout_;
+
+  DISALLOW_COPY_AND_ASSIGN(TimeLossAlgorithm);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CONGESTION_CONTROL_TIME_LOSS_ALGORITHM_H_
diff --git a/net/quic/congestion_control/time_loss_algorithm_test.cc b/net/quic/congestion_control/time_loss_algorithm_test.cc
new file mode 100644
index 0000000..4139019
--- /dev/null
+++ b/net/quic/congestion_control/time_loss_algorithm_test.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/congestion_control/time_loss_algorithm.h"
+#include "net/quic/quic_unacked_packet_map.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class TimeLossAlgorithmTest : public ::testing::Test {
+ protected:
+  TimeLossAlgorithmTest()
+      : unacked_packets_() {
+    rtt_stats_.UpdateRtt(QuicTime::Delta::FromMilliseconds(100),
+                         QuicTime::Delta::Zero(),
+                         clock_.Now());
+  }
+
+  void SendDataPacket(QuicPacketSequenceNumber sequence_number) {
+    SerializedPacket packet(sequence_number, PACKET_1BYTE_SEQUENCE_NUMBER,
+                            nullptr, 0, new RetransmittableFrames());
+    unacked_packets_.AddPacket(packet);
+    unacked_packets_.SetSent(sequence_number, clock_.Now(), 1000, true);
+  }
+
+  void VerifyLosses(QuicPacketSequenceNumber largest_observed,
+                    QuicPacketSequenceNumber* losses_expected,
+                    size_t num_losses) {
+    SequenceNumberSet lost_packets =
+        loss_algorithm_.DetectLostPackets(
+            unacked_packets_, clock_.Now(), largest_observed, rtt_stats_);
+    EXPECT_EQ(num_losses, lost_packets.size());
+    for (size_t i = 0; i < num_losses; ++i) {
+      EXPECT_TRUE(ContainsKey(lost_packets, losses_expected[i]));
+    }
+  }
+
+  QuicUnackedPacketMap unacked_packets_;
+  TimeLossAlgorithm loss_algorithm_;
+  RttStats rtt_stats_;
+  MockClock clock_;
+};
+
+TEST_F(TimeLossAlgorithmTest, NoLossFor500Nacks) {
+  const size_t kNumSentPackets = 5;
+  // Transmit 5 packets.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+  unacked_packets_.RemoveFromInFlight(2);
+  for (size_t i = 1; i < 500; ++i) {
+    unacked_packets_.NackPacket(1, i);
+    VerifyLosses(2, nullptr, 0);
+  }
+  EXPECT_EQ(rtt_stats_.SmoothedRtt().Multiply(1.25),
+            loss_algorithm_.GetLossTimeout().Subtract(clock_.Now()));
+}
+
+TEST_F(TimeLossAlgorithmTest, NoLossUntilTimeout) {
+  const size_t kNumSentPackets = 10;
+  // Transmit 10 packets at 1/10th an RTT interval.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+    clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.1));
+  }
+  // Expect the timer to not be set.
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+  // The packet should not be lost until 1.25 RTTs pass.
+  unacked_packets_.NackPacket(1, 1);
+  unacked_packets_.RemoveFromInFlight(2);
+  VerifyLosses(2, nullptr, 0);
+  // Expect the timer to be set to 0.25 RTT's in the future.
+  EXPECT_EQ(rtt_stats_.SmoothedRtt().Multiply(0.25),
+            loss_algorithm_.GetLossTimeout().Subtract(clock_.Now()));
+  unacked_packets_.NackPacket(1, 5);
+  VerifyLosses(2, nullptr, 0);
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.25));
+  QuicPacketSequenceNumber lost[] = { 1 };
+  VerifyLosses(2, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(TimeLossAlgorithmTest, NoLossWithoutNack) {
+  const size_t kNumSentPackets = 10;
+  // Transmit 10 packets at 1/10th an RTT interval.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+    clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.1));
+  }
+  // Expect the timer to not be set.
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+  // The packet should not be lost without a nack.
+  unacked_packets_.RemoveFromInFlight(1);
+  VerifyLosses(1, nullptr, 0);
+  // The timer should still not be set.
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.25));
+  VerifyLosses(1, nullptr, 0);
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt());
+  VerifyLosses(1, nullptr, 0);
+
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(TimeLossAlgorithmTest, MultipleLossesAtOnce) {
+  const size_t kNumSentPackets = 10;
+  // Transmit 10 packets at once and then go forward an RTT.
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt());
+  // Expect the timer to not be set.
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+  // The packet should not be lost until 1.25 RTTs pass.
+  for (size_t i = 1; i < kNumSentPackets; ++i) {
+    unacked_packets_.NackPacket(i, 1);
+  }
+  unacked_packets_.RemoveFromInFlight(10);
+  VerifyLosses(10, nullptr, 0);
+  // Expect the timer to be set to 0.25 RTT's in the future.
+  EXPECT_EQ(rtt_stats_.SmoothedRtt().Multiply(0.25),
+            loss_algorithm_.GetLossTimeout().Subtract(clock_.Now()));
+  clock_.AdvanceTime(rtt_stats_.SmoothedRtt().Multiply(0.25));
+  QuicPacketSequenceNumber lost[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+  VerifyLosses(10, lost, arraysize(lost));
+  EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/aead_base_decrypter.h b/net/quic/crypto/aead_base_decrypter.h
new file mode 100644
index 0000000..6257409
--- /dev/null
+++ b/net/quic/crypto/aead_base_decrypter.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_AEAD_BASE_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_AEAD_BASE_DECRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_decrypter.h"
+
+#if defined(USE_OPENSSL)
+#include "net/quic/crypto/scoped_evp_aead_ctx.h"
+#else
+#include <pkcs11t.h>
+#include <seccomon.h>
+typedef struct PK11SymKeyStr PK11SymKey;
+typedef SECStatus (*PK11_DecryptFunction)(
+      PK11SymKey* symKey, CK_MECHANISM_TYPE mechanism, SECItem* param,
+      unsigned char* out, unsigned int* outLen, unsigned int maxLen,
+      const unsigned char* enc, unsigned encLen);
+#endif
+
+namespace net {
+
+// AeadBaseDecrypter is the base class of AEAD QuicDecrypter subclasses.
+class NET_EXPORT_PRIVATE AeadBaseDecrypter : public QuicDecrypter {
+ public:
+#if defined(USE_OPENSSL)
+  AeadBaseDecrypter(const EVP_AEAD* aead_alg,
+                    size_t key_size,
+                    size_t auth_tag_size,
+                    size_t nonce_prefix_size);
+#else
+  AeadBaseDecrypter(CK_MECHANISM_TYPE aead_mechanism,
+                    PK11_DecryptFunction pk11_decrypt,
+                    size_t key_size,
+                    size_t auth_tag_size,
+                    size_t nonce_prefix_size);
+#endif
+  virtual ~AeadBaseDecrypter();
+
+  // QuicDecrypter implementation
+  virtual bool SetKey(base::StringPiece key) OVERRIDE;
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+  virtual bool Decrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece ciphertext,
+                       unsigned char* output,
+                       size_t* output_length) OVERRIDE;
+  virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece ciphertext) OVERRIDE;
+  virtual base::StringPiece GetKey() const OVERRIDE;
+  virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ protected:
+  // Make these constants available to the subclasses so that the subclasses
+  // can assert at compile time their key_size_ and nonce_prefix_size_ do not
+  // exceed the maximum.
+  static const size_t kMaxKeySize = 32;
+  static const size_t kMaxNoncePrefixSize = 4;
+
+#if !defined(USE_OPENSSL)
+  struct AeadParams {
+    unsigned int len;
+    union {
+      CK_GCM_PARAMS gcm_params;
+#if !defined(USE_NSS)
+      // USE_NSS means we are using system NSS rather than our copy of NSS.
+      // The system NSS <pkcs11n.h> header doesn't define this type yet.
+      CK_NSS_AEAD_PARAMS nss_aead_params;
+#endif
+    } data;
+  };
+
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const = 0;
+#endif  // !defined(USE_OPENSSL)
+
+ private:
+#if defined(USE_OPENSSL)
+  const EVP_AEAD* const aead_alg_;
+#else
+  const CK_MECHANISM_TYPE aead_mechanism_;
+  const PK11_DecryptFunction pk11_decrypt_;
+#endif
+  const size_t key_size_;
+  const size_t auth_tag_size_;
+  const size_t nonce_prefix_size_;
+
+  // The key.
+  unsigned char key_[kMaxKeySize];
+  // The nonce prefix.
+  unsigned char nonce_prefix_[kMaxNoncePrefixSize];
+
+#if defined(USE_OPENSSL)
+  ScopedEVPAEADCtx ctx_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(AeadBaseDecrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_AEAD_BASE_DECRYPTER_H_
diff --git a/net/quic/crypto/aead_base_decrypter_nss.cc b/net/quic/crypto/aead_base_decrypter_nss.cc
new file mode 100644
index 0000000..2401222
--- /dev/null
+++ b/net/quic/crypto/aead_base_decrypter_nss.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aead_base_decrypter.h"
+
+#include <pk11pub.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "crypto/scoped_nss_types.h"
+
+using base::StringPiece;
+
+namespace net {
+
+AeadBaseDecrypter::AeadBaseDecrypter(CK_MECHANISM_TYPE aead_mechanism,
+                                     PK11_DecryptFunction pk11_decrypt,
+                                     size_t key_size,
+                                     size_t auth_tag_size,
+                                     size_t nonce_prefix_size)
+    : aead_mechanism_(aead_mechanism),
+      pk11_decrypt_(pk11_decrypt),
+      key_size_(key_size),
+      auth_tag_size_(auth_tag_size),
+      nonce_prefix_size_(nonce_prefix_size) {
+  DCHECK_LE(key_size_, sizeof(key_));
+  DCHECK_LE(nonce_prefix_size_, sizeof(nonce_prefix_));
+}
+
+AeadBaseDecrypter::~AeadBaseDecrypter() {}
+
+bool AeadBaseDecrypter::SetKey(StringPiece key) {
+  DCHECK_EQ(key.size(), key_size_);
+  if (key.size() != key_size_) {
+    return false;
+  }
+  memcpy(key_, key.data(), key.size());
+  return true;
+}
+
+bool AeadBaseDecrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  DCHECK_EQ(nonce_prefix.size(), nonce_prefix_size_);
+  if (nonce_prefix.size() != nonce_prefix_size_) {
+    return false;
+  }
+  memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+  return true;
+}
+
+bool AeadBaseDecrypter::Decrypt(StringPiece nonce,
+                                StringPiece associated_data,
+                                StringPiece ciphertext,
+                                uint8* output,
+                                size_t* output_length) {
+  if (ciphertext.length() < auth_tag_size_ ||
+      nonce.size() != nonce_prefix_size_ + sizeof(QuicPacketSequenceNumber)) {
+    return false;
+  }
+  // NSS 3.14.x incorrectly requires an output buffer at least as long as
+  // the ciphertext (NSS bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id= 853674). Fortunately
+  // QuicDecrypter::Decrypt() specifies that |output| must be as long as
+  // |ciphertext| on entry.
+  size_t plaintext_size = ciphertext.length() - auth_tag_size_;
+
+  // Import key_ into NSS.
+  SECItem key_item;
+  key_item.type = siBuffer;
+  key_item.data = key_;
+  key_item.len = key_size_;
+  PK11SlotInfo* slot = PK11_GetInternalSlot();
+
+  // TODO(wtc): For an AES-GCM key, the correct value for |key_mechanism| is
+  // CKM_AES_GCM, but because of NSS bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=853285, use CKM_AES_ECB as a
+  // workaround. Remove this when we require NSS 3.15.
+  CK_MECHANISM_TYPE key_mechanism = aead_mechanism_;
+  if (key_mechanism == CKM_AES_GCM) {
+    key_mechanism = CKM_AES_ECB;
+  }
+
+  // The exact value of the |origin| argument doesn't matter to NSS as long as
+  // it's not PK11_OriginFortezzaHack, so pass PK11_OriginUnwrap as a
+  // placeholder.
+  crypto::ScopedPK11SymKey aead_key(PK11_ImportSymKey(
+      slot, key_mechanism, PK11_OriginUnwrap, CKA_DECRYPT, &key_item, nullptr));
+  PK11_FreeSlot(slot);
+  slot = nullptr;
+  if (!aead_key) {
+    DVLOG(1) << "PK11_ImportSymKey failed";
+    return false;
+  }
+
+  AeadParams aead_params = {0};
+  FillAeadParams(nonce, associated_data, auth_tag_size_, &aead_params);
+
+  SECItem param;
+  param.type = siBuffer;
+  param.data = reinterpret_cast<unsigned char*>(&aead_params.data);
+  param.len = aead_params.len;
+
+  unsigned int output_len;
+  if (pk11_decrypt_(aead_key.get(), aead_mechanism_, &param,
+                    output, &output_len, ciphertext.length(),
+                    reinterpret_cast<const unsigned char*>(ciphertext.data()),
+                    ciphertext.length()) != SECSuccess) {
+    return false;
+  }
+
+  if (output_len != plaintext_size) {
+    DVLOG(1) << "Wrong output length";
+    return false;
+  }
+  *output_length = output_len;
+  return true;
+}
+
+QuicData* AeadBaseDecrypter::DecryptPacket(
+    QuicPacketSequenceNumber sequence_number,
+    StringPiece associated_data,
+    StringPiece ciphertext) {
+  if (ciphertext.length() < auth_tag_size_) {
+    return nullptr;
+  }
+  size_t plaintext_size;
+  scoped_ptr<char[]> plaintext(new char[ciphertext.length()]);
+
+  uint8 nonce[sizeof(nonce_prefix_) + sizeof(sequence_number)];
+  const size_t nonce_size = nonce_prefix_size_ + sizeof(sequence_number);
+  DCHECK_LE(nonce_size, sizeof(nonce));
+  memcpy(nonce, nonce_prefix_, nonce_prefix_size_);
+  memcpy(nonce + nonce_prefix_size_, &sequence_number, sizeof(sequence_number));
+  if (!Decrypt(StringPiece(reinterpret_cast<char*>(nonce), nonce_size),
+               associated_data, ciphertext,
+               reinterpret_cast<uint8*>(plaintext.get()),
+               &plaintext_size)) {
+    return nullptr;
+  }
+  return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+StringPiece AeadBaseDecrypter::GetKey() const {
+  return StringPiece(reinterpret_cast<const char*>(key_), key_size_);
+}
+
+StringPiece AeadBaseDecrypter::GetNoncePrefix() const {
+  if (nonce_prefix_size_ == 0) {
+    return StringPiece();
+  }
+  return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+                     nonce_prefix_size_);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aead_base_decrypter_openssl.cc b/net/quic/crypto/aead_base_decrypter_openssl.cc
new file mode 100644
index 0000000..62a6fb8
--- /dev/null
+++ b/net/quic/crypto/aead_base_decrypter_openssl.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aead_base_decrypter.h"
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+
+#include "base/memory/scoped_ptr.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+// Clear OpenSSL error stack.
+void ClearOpenSslErrors() {
+  while (ERR_get_error()) {}
+}
+
+// In debug builds only, log OpenSSL error stack. Then clear OpenSSL error
+// stack.
+void DLogOpenSslErrors() {
+#ifdef NDEBUG
+  ClearOpenSslErrors();
+#else
+  while (unsigned long error = ERR_get_error()) {
+    char buf[120];
+    ERR_error_string_n(error, buf, arraysize(buf));
+    DLOG(ERROR) << "OpenSSL error: " << buf;
+  }
+#endif
+}
+
+}  // namespace
+
+AeadBaseDecrypter::AeadBaseDecrypter(const EVP_AEAD* aead_alg,
+                                     size_t key_size,
+                                     size_t auth_tag_size,
+                                     size_t nonce_prefix_size)
+    : aead_alg_(aead_alg),
+      key_size_(key_size),
+      auth_tag_size_(auth_tag_size),
+      nonce_prefix_size_(nonce_prefix_size) {
+  DCHECK_LE(key_size_, sizeof(key_));
+  DCHECK_LE(nonce_prefix_size_, sizeof(nonce_prefix_));
+}
+
+AeadBaseDecrypter::~AeadBaseDecrypter() {}
+
+bool AeadBaseDecrypter::SetKey(StringPiece key) {
+  DCHECK_EQ(key.size(), key_size_);
+  if (key.size() != key_size_) {
+    return false;
+  }
+  memcpy(key_, key.data(), key.size());
+
+  EVP_AEAD_CTX_cleanup(ctx_.get());
+  if (!EVP_AEAD_CTX_init(ctx_.get(), aead_alg_, key_, key_size_,
+                         auth_tag_size_, nullptr)) {
+    DLogOpenSslErrors();
+    return false;
+  }
+
+  return true;
+}
+
+bool AeadBaseDecrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  DCHECK_EQ(nonce_prefix.size(), nonce_prefix_size_);
+  if (nonce_prefix.size() != nonce_prefix_size_) {
+    return false;
+  }
+  memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+  return true;
+}
+
+bool AeadBaseDecrypter::Decrypt(StringPiece nonce,
+                                StringPiece associated_data,
+                                StringPiece ciphertext,
+                                uint8* output,
+                                size_t* output_length) {
+  if (ciphertext.length() < auth_tag_size_ ||
+      nonce.size() != nonce_prefix_size_ + sizeof(QuicPacketSequenceNumber)) {
+    return false;
+  }
+
+  if (!EVP_AEAD_CTX_open(
+      ctx_.get(), output, output_length, ciphertext.size(),
+      reinterpret_cast<const uint8_t*>(nonce.data()), nonce.size(),
+      reinterpret_cast<const uint8_t*>(ciphertext.data()), ciphertext.size(),
+      reinterpret_cast<const uint8_t*>(associated_data.data()),
+      associated_data.size())) {
+    // Because QuicFramer does trial decryption, decryption errors are expected
+    // when encryption level changes. So we don't log decryption errors.
+    ClearOpenSslErrors();
+    return false;
+  }
+
+  return true;
+}
+
+QuicData* AeadBaseDecrypter::DecryptPacket(
+    QuicPacketSequenceNumber sequence_number,
+    StringPiece associated_data,
+    StringPiece ciphertext) {
+  if (ciphertext.length() < auth_tag_size_) {
+    return nullptr;
+  }
+  size_t plaintext_size = ciphertext.length();
+  scoped_ptr<char[]> plaintext(new char[plaintext_size]);
+
+  uint8 nonce[sizeof(nonce_prefix_) + sizeof(sequence_number)];
+  const size_t nonce_size = nonce_prefix_size_ + sizeof(sequence_number);
+  DCHECK_LE(nonce_size, sizeof(nonce));
+  memcpy(nonce, nonce_prefix_, nonce_prefix_size_);
+  memcpy(nonce + nonce_prefix_size_, &sequence_number, sizeof(sequence_number));
+  if (!Decrypt(StringPiece(reinterpret_cast<char*>(nonce), nonce_size),
+               associated_data, ciphertext,
+               reinterpret_cast<uint8*>(plaintext.get()),
+               &plaintext_size)) {
+    return nullptr;
+  }
+  return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+StringPiece AeadBaseDecrypter::GetKey() const {
+  return StringPiece(reinterpret_cast<const char*>(key_), key_size_);
+}
+
+StringPiece AeadBaseDecrypter::GetNoncePrefix() const {
+  if (nonce_prefix_size_ == 0) {
+    return StringPiece();
+  }
+  return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+                     nonce_prefix_size_);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aead_base_encrypter.h b/net/quic/crypto/aead_base_encrypter.h
new file mode 100644
index 0000000..7138131
--- /dev/null
+++ b/net/quic/crypto/aead_base_encrypter.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_AEAD_BASE_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_AEAD_BASE_ENCRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+#if defined(USE_OPENSSL)
+#include "net/quic/crypto/scoped_evp_aead_ctx.h"
+#else
+#include <pkcs11t.h>
+#include <seccomon.h>
+typedef struct PK11SymKeyStr PK11SymKey;
+typedef SECStatus (*PK11_EncryptFunction)(
+    PK11SymKey* symKey, CK_MECHANISM_TYPE mechanism, SECItem* param,
+    unsigned char* out, unsigned int* outLen, unsigned int maxLen,
+    const unsigned char* data, unsigned int dataLen);
+#endif
+
+namespace net {
+
+// AeadBaseEncrypter is the base class of AEAD QuicEncrypter subclasses.
+class NET_EXPORT_PRIVATE AeadBaseEncrypter : public QuicEncrypter {
+ public:
+#if defined(USE_OPENSSL)
+  AeadBaseEncrypter(const EVP_AEAD* aead_alg,
+                    size_t key_size,
+                    size_t auth_tag_size,
+                    size_t nonce_prefix_size);
+#else
+  AeadBaseEncrypter(CK_MECHANISM_TYPE aead_mechanism,
+                    PK11_EncryptFunction pk11_encrypt,
+                    size_t key_size,
+                    size_t auth_tag_size,
+                    size_t nonce_prefix_size);
+#endif
+  virtual ~AeadBaseEncrypter();
+
+  // QuicEncrypter implementation
+  virtual bool SetKey(base::StringPiece key) OVERRIDE;
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+  virtual bool Encrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece plaintext,
+                       unsigned char* output) OVERRIDE;
+  virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece plaintext) OVERRIDE;
+  virtual size_t GetKeySize() const OVERRIDE;
+  virtual size_t GetNoncePrefixSize() const OVERRIDE;
+  virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE;
+  virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE;
+  virtual base::StringPiece GetKey() const OVERRIDE;
+  virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ protected:
+  // Make these constants available to the subclasses so that the subclasses
+  // can assert at compile time their key_size_ and nonce_prefix_size_ do not
+  // exceed the maximum.
+  static const size_t kMaxKeySize = 32;
+  static const size_t kMaxNoncePrefixSize = 4;
+
+#if !defined(USE_OPENSSL)
+  struct AeadParams {
+    unsigned int len;
+    union {
+      CK_GCM_PARAMS gcm_params;
+#if !defined(USE_NSS)
+      // USE_NSS means we are using system NSS rather than our copy of NSS.
+      // The system NSS <pkcs11n.h> header doesn't define this type yet.
+      CK_NSS_AEAD_PARAMS nss_aead_params;
+#endif
+    } data;
+  };
+
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const = 0;
+#endif
+
+ private:
+#if defined(USE_OPENSSL)
+  const EVP_AEAD* const aead_alg_;
+#else
+  const CK_MECHANISM_TYPE aead_mechanism_;
+  const PK11_EncryptFunction pk11_encrypt_;
+#endif
+  const size_t key_size_;
+  const size_t auth_tag_size_;
+  const size_t nonce_prefix_size_;
+
+  // The key.
+  unsigned char key_[kMaxKeySize];
+  // The nonce prefix.
+  unsigned char nonce_prefix_[kMaxNoncePrefixSize];
+
+#if defined(USE_OPENSSL)
+  ScopedEVPAEADCtx ctx_;
+#endif
+
+  DISALLOW_COPY_AND_ASSIGN(AeadBaseEncrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_AEAD_BASE_ENCRYPTER_H_
diff --git a/net/quic/crypto/aead_base_encrypter_nss.cc b/net/quic/crypto/aead_base_encrypter_nss.cc
new file mode 100644
index 0000000..1e408ad
--- /dev/null
+++ b/net/quic/crypto/aead_base_encrypter_nss.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aead_base_encrypter.h"
+
+#include <pk11pub.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "crypto/scoped_nss_types.h"
+
+using base::StringPiece;
+
+namespace net {
+
+AeadBaseEncrypter::AeadBaseEncrypter(CK_MECHANISM_TYPE aead_mechanism,
+                                     PK11_EncryptFunction pk11_encrypt,
+                                     size_t key_size,
+                                     size_t auth_tag_size,
+                                     size_t nonce_prefix_size)
+    : aead_mechanism_(aead_mechanism),
+      pk11_encrypt_(pk11_encrypt),
+      key_size_(key_size),
+      auth_tag_size_(auth_tag_size),
+      nonce_prefix_size_(nonce_prefix_size) {
+  DCHECK_LE(key_size_, sizeof(key_));
+  DCHECK_LE(nonce_prefix_size_, sizeof(nonce_prefix_));
+}
+
+AeadBaseEncrypter::~AeadBaseEncrypter() {}
+
+bool AeadBaseEncrypter::SetKey(StringPiece key) {
+  DCHECK_EQ(key.size(), key_size_);
+  if (key.size() != key_size_) {
+    return false;
+  }
+  memcpy(key_, key.data(), key.size());
+  return true;
+}
+
+bool AeadBaseEncrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  DCHECK_EQ(nonce_prefix.size(), nonce_prefix_size_);
+  if (nonce_prefix.size() != nonce_prefix_size_) {
+    return false;
+  }
+  memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+  return true;
+}
+
+bool AeadBaseEncrypter::Encrypt(StringPiece nonce,
+                                StringPiece associated_data,
+                                StringPiece plaintext,
+                                unsigned char* output) {
+  if (nonce.size() != nonce_prefix_size_ + sizeof(QuicPacketSequenceNumber)) {
+    return false;
+  }
+
+  size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+
+  // Import key_ into NSS.
+  SECItem key_item;
+  key_item.type = siBuffer;
+  key_item.data = key_;
+  key_item.len = key_size_;
+  PK11SlotInfo* slot = PK11_GetInternalSlot();
+
+  // TODO(wtc): For an AES-GCM key, the correct value for |key_mechanism| is
+  // CKM_AES_GCM, but because of NSS bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=853285, use CKM_AES_ECB as a
+  // workaround. Remove this when we require NSS 3.15.
+  CK_MECHANISM_TYPE key_mechanism = aead_mechanism_;
+  if (key_mechanism == CKM_AES_GCM) {
+    key_mechanism = CKM_AES_ECB;
+  }
+
+  // The exact value of the |origin| argument doesn't matter to NSS as long as
+  // it's not PK11_OriginFortezzaHack, so we pass PK11_OriginUnwrap as a
+  // placeholder.
+  crypto::ScopedPK11SymKey aead_key(PK11_ImportSymKey(
+      slot, key_mechanism, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, nullptr));
+  PK11_FreeSlot(slot);
+  slot = nullptr;
+  if (!aead_key) {
+    DVLOG(1) << "PK11_ImportSymKey failed";
+    return false;
+  }
+
+  AeadParams aead_params = {0};
+  FillAeadParams(nonce, associated_data, auth_tag_size_, &aead_params);
+
+  SECItem param;
+  param.type = siBuffer;
+  param.data = reinterpret_cast<unsigned char*>(&aead_params.data);
+  param.len = aead_params.len;
+
+  unsigned int output_len;
+  if (pk11_encrypt_(aead_key.get(), aead_mechanism_, &param,
+                    output, &output_len, ciphertext_size,
+                    reinterpret_cast<const unsigned char*>(plaintext.data()),
+                    plaintext.size()) != SECSuccess) {
+    DVLOG(1) << "pk11_encrypt_ failed";
+    return false;
+  }
+
+  if (output_len != ciphertext_size) {
+    DVLOG(1) << "Wrong output length";
+    return false;
+  }
+
+  return true;
+}
+
+QuicData* AeadBaseEncrypter::EncryptPacket(
+    QuicPacketSequenceNumber sequence_number,
+    StringPiece associated_data,
+    StringPiece plaintext) {
+  size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+  scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+  // TODO(ianswett): Introduce a check to ensure that we don't encrypt with the
+  // same sequence number twice.
+  uint8 nonce[sizeof(nonce_prefix_) + sizeof(sequence_number)];
+  const size_t nonce_size = nonce_prefix_size_ + sizeof(sequence_number);
+  DCHECK_LE(nonce_size, sizeof(nonce));
+  memcpy(nonce, nonce_prefix_, nonce_prefix_size_);
+  memcpy(nonce + nonce_prefix_size_, &sequence_number, sizeof(sequence_number));
+  if (!Encrypt(StringPiece(reinterpret_cast<char*>(nonce), nonce_size),
+               associated_data, plaintext,
+               reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+    return nullptr;
+  }
+
+  return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+size_t AeadBaseEncrypter::GetKeySize() const { return key_size_; }
+
+size_t AeadBaseEncrypter::GetNoncePrefixSize() const {
+  return nonce_prefix_size_;
+}
+
+size_t AeadBaseEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+  return ciphertext_size - auth_tag_size_;
+}
+
+size_t AeadBaseEncrypter::GetCiphertextSize(size_t plaintext_size) const {
+  return plaintext_size + auth_tag_size_;
+}
+
+StringPiece AeadBaseEncrypter::GetKey() const {
+  return StringPiece(reinterpret_cast<const char*>(key_), key_size_);
+}
+
+StringPiece AeadBaseEncrypter::GetNoncePrefix() const {
+  if (nonce_prefix_size_ == 0) {
+    return StringPiece();
+  }
+  return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+                     nonce_prefix_size_);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aead_base_encrypter_openssl.cc b/net/quic/crypto/aead_base_encrypter_openssl.cc
new file mode 100644
index 0000000..25d9967
--- /dev/null
+++ b/net/quic/crypto/aead_base_encrypter_openssl.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aead_base_encrypter.h"
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <string.h>
+
+#include "base/memory/scoped_ptr.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+// In debug builds only, log OpenSSL error stack. Then clear OpenSSL error
+// stack.
+void DLogOpenSslErrors() {
+#ifdef NDEBUG
+  while (ERR_get_error()) {}
+#else
+  while (unsigned long error = ERR_get_error()) {
+    char buf[120];
+    ERR_error_string_n(error, buf, arraysize(buf));
+    DLOG(ERROR) << "OpenSSL error: " << buf;
+  }
+#endif
+}
+
+}  // namespace
+
+AeadBaseEncrypter::AeadBaseEncrypter(const EVP_AEAD* aead_alg,
+                                     size_t key_size,
+                                     size_t auth_tag_size,
+                                     size_t nonce_prefix_size)
+    : aead_alg_(aead_alg),
+      key_size_(key_size),
+      auth_tag_size_(auth_tag_size),
+      nonce_prefix_size_(nonce_prefix_size) {
+  DCHECK_LE(key_size_, sizeof(key_));
+  DCHECK_LE(nonce_prefix_size_, sizeof(nonce_prefix_));
+}
+
+AeadBaseEncrypter::~AeadBaseEncrypter() {}
+
+bool AeadBaseEncrypter::SetKey(StringPiece key) {
+  DCHECK_EQ(key.size(), key_size_);
+  if (key.size() != key_size_) {
+    return false;
+  }
+  memcpy(key_, key.data(), key.size());
+
+  EVP_AEAD_CTX_cleanup(ctx_.get());
+
+  if (!EVP_AEAD_CTX_init(ctx_.get(), aead_alg_, key_, key_size_,
+                         auth_tag_size_, nullptr)) {
+    DLogOpenSslErrors();
+    return false;
+  }
+
+  return true;
+}
+
+bool AeadBaseEncrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  DCHECK_EQ(nonce_prefix.size(), nonce_prefix_size_);
+  if (nonce_prefix.size() != nonce_prefix_size_) {
+    return false;
+  }
+  memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+  return true;
+}
+
+bool AeadBaseEncrypter::Encrypt(StringPiece nonce,
+                                StringPiece associated_data,
+                                StringPiece plaintext,
+                                unsigned char* output) {
+  if (nonce.size() != nonce_prefix_size_ + sizeof(QuicPacketSequenceNumber)) {
+    return false;
+  }
+
+  size_t len;
+  if (!EVP_AEAD_CTX_seal(
+          ctx_.get(),
+          output,
+          &len,
+          plaintext.size() + auth_tag_size_,
+          reinterpret_cast<const uint8_t*>(nonce.data()),
+          nonce.size(),
+          reinterpret_cast<const uint8_t*>(plaintext.data()),
+          plaintext.size(),
+          reinterpret_cast<const uint8_t*>(associated_data.data()),
+          associated_data.size())) {
+    DLogOpenSslErrors();
+    return false;
+  }
+
+  return true;
+}
+
+QuicData* AeadBaseEncrypter::EncryptPacket(
+    QuicPacketSequenceNumber sequence_number,
+    StringPiece associated_data,
+    StringPiece plaintext) {
+  size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+  scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+  // TODO(ianswett): Introduce a check to ensure that we don't encrypt with the
+  // same sequence number twice.
+  uint8 nonce[sizeof(nonce_prefix_) + sizeof(sequence_number)];
+  const size_t nonce_size = nonce_prefix_size_ + sizeof(sequence_number);
+  DCHECK_LE(nonce_size, sizeof(nonce));
+  memcpy(nonce, nonce_prefix_, nonce_prefix_size_);
+  memcpy(nonce + nonce_prefix_size_, &sequence_number, sizeof(sequence_number));
+  if (!Encrypt(StringPiece(reinterpret_cast<char*>(nonce), nonce_size),
+               associated_data, plaintext,
+               reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+    return nullptr;
+  }
+
+  return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+size_t AeadBaseEncrypter::GetKeySize() const { return key_size_; }
+
+size_t AeadBaseEncrypter::GetNoncePrefixSize() const {
+  return nonce_prefix_size_;
+}
+
+size_t AeadBaseEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+  return ciphertext_size - auth_tag_size_;
+}
+
+size_t AeadBaseEncrypter::GetCiphertextSize(size_t plaintext_size) const {
+  return plaintext_size + auth_tag_size_;
+}
+
+StringPiece AeadBaseEncrypter::GetKey() const {
+  return StringPiece(reinterpret_cast<const char*>(key_), key_size_);
+}
+
+StringPiece AeadBaseEncrypter::GetNoncePrefix() const {
+  if (nonce_prefix_size_ == 0) {
+    return StringPiece();
+  }
+  return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+                     nonce_prefix_size_);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_decrypter.h b/net/quic/crypto/aes_128_gcm_12_decrypter.h
new file mode 100644
index 0000000..0511c8b
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_decrypter.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
+
+#include "net/quic/crypto/aead_base_decrypter.h"
+
+namespace net {
+
+// An Aes128Gcm12Decrypter is a QuicDecrypter that implements the
+// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by
+// calling QuicDecrypter::Create(kAESG).
+//
+// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix
+// of the nonce is four bytes.
+class NET_EXPORT_PRIVATE Aes128Gcm12Decrypter : public AeadBaseDecrypter {
+ public:
+  enum {
+    // Authentication tags are truncated to 96 bits.
+    kAuthTagSize = 12,
+  };
+
+  Aes128Gcm12Decrypter();
+  virtual ~Aes128Gcm12Decrypter();
+
+#if !defined(USE_OPENSSL)
+ protected:
+  // AeadBaseDecrypter methods:
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const OVERRIDE;
+#endif
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Aes128Gcm12Decrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
diff --git a/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc b/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc
new file mode 100644
index 0000000..d2cd728
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include <pk11pub.h>
+#include <secerr.h>
+
+#include "base/lazy_instance.h"
+#include "crypto/ghash.h"
+#include "crypto/scoped_nss_types.h"
+
+#if defined(USE_NSS)
+#include <dlfcn.h>
+#endif
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+
+// On Linux, dynamically link against the system version of libnss3.so. In
+// order to continue working on systems without up-to-date versions of NSS,
+// lookup PK11_Decrypt with dlsym.
+
+// GcmSupportChecker is a singleton which caches the results of runtime symbol
+// resolution of PK11_Decrypt.
+class GcmSupportChecker {
+ public:
+  static PK11_DecryptFunction pk11_decrypt_func() {
+    return pk11_decrypt_func_;
+  }
+
+ private:
+  friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>;
+
+  GcmSupportChecker() {
+#if !defined(USE_NSS)
+    // Using a bundled version of NSS that is guaranteed to have this symbol.
+    pk11_decrypt_func_ = PK11_Decrypt;
+#else
+    // Using system NSS libraries and PCKS #11 modules, which may not have the
+    // necessary function (PK11_Decrypt) or mechanism support (CKM_AES_GCM).
+
+    // If PK11_Decrypt() was successfully resolved, then NSS will support
+    // AES-GCM directly. This was introduced in NSS 3.15.
+    pk11_decrypt_func_ = (PK11_DecryptFunction)dlsym(RTLD_DEFAULT,
+                                                     "PK11_Decrypt");
+#endif
+  }
+
+  // |pk11_decrypt_func_| stores the runtime symbol resolution of PK11_Decrypt.
+  static PK11_DecryptFunction pk11_decrypt_func_;
+};
+
+// static
+PK11_DecryptFunction GcmSupportChecker::pk11_decrypt_func_ = nullptr;
+
+base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker =
+    LAZY_INSTANCE_INITIALIZER;
+
+// Calls PK11_Decrypt if it's available.  Otherwise, emulates CKM_AES_GCM using
+// CKM_AES_CTR and the GaloisHash class.
+SECStatus My_Decrypt(PK11SymKey* key,
+                     CK_MECHANISM_TYPE mechanism,
+                     SECItem* param,
+                     unsigned char* out,
+                     unsigned int* out_len,
+                     unsigned int max_len,
+                     const unsigned char* enc,
+                     unsigned int enc_len) {
+  // If PK11_Decrypt() was successfully resolved or if bundled version of NSS is
+  // being used, then NSS will support AES-GCM directly.
+  PK11_DecryptFunction pk11_decrypt_func =
+      GcmSupportChecker::pk11_decrypt_func();
+  if (pk11_decrypt_func != nullptr) {
+    return pk11_decrypt_func(key, mechanism, param, out, out_len, max_len, enc,
+                             enc_len);
+  }
+
+  // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x
+  // has a bug in the AES GCM code
+  // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing
+  // the PK11_Decrypt function
+  // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are
+  // resolved in NSS 3.15.
+
+  DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM));
+  DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS));
+
+  const CK_GCM_PARAMS* gcm_params =
+      reinterpret_cast<CK_GCM_PARAMS*>(param->data);
+
+  DCHECK_EQ(gcm_params->ulTagBits,
+            static_cast<CK_ULONG>(Aes128Gcm12Decrypter::kAuthTagSize * 8));
+  if (gcm_params->ulIvLen != 12u) {
+    DVLOG(1) << "ulIvLen is not equal to 12";
+    PORT_SetError(SEC_ERROR_INPUT_LEN);
+    return SECFailure;
+  }
+
+  SECItem my_param = { siBuffer, nullptr, 0 };
+
+  // Step 2. Let H = CIPH_K(128 '0' bits).
+  unsigned char ghash_key[16] = {0};
+  crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey(
+      CKM_AES_ECB, CKA_ENCRYPT, key, &my_param));
+  if (!ctx) {
+    DVLOG(1) << "PK11_CreateContextBySymKey failed";
+    return SECFailure;
+  }
+  int output_len;
+  if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key),
+                    ghash_key, sizeof(ghash_key)) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+
+  PK11_Finalize(ctx.get());
+
+  if (output_len != sizeof(ghash_key)) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  // Step 3. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1.
+  CK_AES_CTR_PARAMS ctr_params = {0};
+  ctr_params.ulCounterBits = 32;
+  memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen);
+  ctr_params.cb[12] = 0;
+  ctr_params.cb[13] = 0;
+  ctr_params.cb[14] = 0;
+  ctr_params.cb[15] = 1;
+
+  my_param.type = siBuffer;
+  my_param.data = reinterpret_cast<unsigned char*>(&ctr_params);
+  my_param.len = sizeof(ctr_params);
+
+  ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key,
+                                       &my_param));
+  if (!ctx) {
+    DVLOG(1) << "PK11_CreateContextBySymKey failed";
+    return SECFailure;
+  }
+
+  // Step 6. Calculate the encryption mask of GCTR_K(J0, ...).
+  unsigned char tag_mask[16] = {0};
+  if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask),
+                    tag_mask, sizeof(tag_mask)) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+  if (output_len != sizeof(tag_mask)) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  if (enc_len < Aes128Gcm12Decrypter::kAuthTagSize) {
+    PORT_SetError(SEC_ERROR_INPUT_LEN);
+    return SECFailure;
+  }
+
+  // The const_cast for |enc| can be removed if system NSS libraries are
+  // NSS 3.14.1 or later (NSS bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=808218).
+  if (PK11_CipherOp(ctx.get(), out, &output_len, max_len,
+          const_cast<unsigned char*>(enc),
+          enc_len - Aes128Gcm12Decrypter::kAuthTagSize) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+
+  PK11_Finalize(ctx.get());
+
+  if (static_cast<unsigned int>(output_len) !=
+      enc_len - Aes128Gcm12Decrypter::kAuthTagSize) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  crypto::GaloisHash ghash(ghash_key);
+  ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen);
+  ghash.UpdateCiphertext(enc, output_len);
+  unsigned char auth_tag[Aes128Gcm12Decrypter::kAuthTagSize];
+  ghash.Finish(auth_tag, Aes128Gcm12Decrypter::kAuthTagSize);
+  for (unsigned int i = 0; i < Aes128Gcm12Decrypter::kAuthTagSize; i++) {
+    auth_tag[i] ^= tag_mask[i];
+  }
+
+  if (NSS_SecureMemcmp(auth_tag, enc + output_len,
+                       Aes128Gcm12Decrypter::kAuthTagSize) != 0) {
+    PORT_SetError(SEC_ERROR_BAD_DATA);
+    return SECFailure;
+  }
+
+  *out_len = output_len;
+  return SECSuccess;
+}
+
+}  // namespace
+
+Aes128Gcm12Decrypter::Aes128Gcm12Decrypter()
+    : AeadBaseDecrypter(CKM_AES_GCM, My_Decrypt, kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+  ignore_result(g_gcm_support_checker.Get());
+}
+
+Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {}
+
+void Aes128Gcm12Decrypter::FillAeadParams(StringPiece nonce,
+                                          StringPiece associated_data,
+                                          size_t auth_tag_size,
+                                          AeadParams* aead_params) const {
+  aead_params->len = sizeof(aead_params->data.gcm_params);
+  CK_GCM_PARAMS* gcm_params = &aead_params->data.gcm_params;
+  gcm_params->pIv =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+  gcm_params->ulIvLen = nonce.size();
+  gcm_params->pAAD =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+  gcm_params->ulAADLen = associated_data.size();
+  gcm_params->ulTagBits = auth_tag_size * 8;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc b/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc
new file mode 100644
index 0000000..109d2da
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include <openssl/evp.h>
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+
+}  // namespace
+
+Aes128Gcm12Decrypter::Aes128Gcm12Decrypter()
+    : AeadBaseDecrypter(EVP_aead_aes_128_gcm(), kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {}
+
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc b/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc
new file mode 100644
index 0000000..37ee8f9
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The AES GCM test vectors come from the file gcmDecrypt128.rsp
+// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on
+// 2013-02-01. The test vectors in that file look like this:
+//
+// [Keylen = 128]
+// [IVlen = 96]
+// [PTlen = 0]
+// [AADlen = 0]
+// [Taglen = 128]
+//
+// Count = 0
+// Key = cf063a34d4a9a76c2c86787d3f96db71
+// IV = 113b9785971864c83b01c787
+// CT =
+// AAD =
+// Tag = 72ac8493e3a5228b5d130a69d2510e42
+// PT =
+//
+// Count = 1
+// Key = a49a5e26a2f8cb63d05546c2a62f5343
+// IV = 907763b19b9b4ab6bd4f0281
+// CT =
+// AAD =
+// Tag = a2be08210d8c470a8df6e8fbd79ec5cf
+// FAIL
+//
+// ...
+//
+// The gcmDecrypt128.rsp file is huge (2.6 MB), so I selected just a
+// few test vectors for this unit test.
+
+// Describes a group of test vectors that all have a given key length, IV
+// length, plaintext length, AAD length, and tag length.
+struct TestGroupInfo {
+  size_t key_len;
+  size_t iv_len;
+  size_t pt_len;
+  size_t aad_len;
+  size_t tag_len;
+};
+
+// Each test vector consists of six strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+  // Input:
+  const char* key;
+  const char* iv;
+  const char* ct;
+  const char* aad;
+  const char* tag;
+
+  // Expected output:
+  const char* pt;  // An empty string "" means decryption succeeded and
+                   // the plaintext is zero-length. NULL means decryption
+                   // failed.
+};
+
+const TestGroupInfo test_group_info[] = {
+  { 128, 96, 0, 0, 128 },
+  { 128, 96, 0, 128, 128 },
+  { 128, 96, 128, 0, 128 },
+  { 128, 96, 408, 160, 128 },
+  { 128, 96, 408, 720, 128 },
+  { 128, 96, 104, 0, 128 },
+};
+
+const TestVector test_group_0[] = {
+  { "cf063a34d4a9a76c2c86787d3f96db71",
+    "113b9785971864c83b01c787",
+    "",
+    "",
+    "72ac8493e3a5228b5d130a69d2510e42",
+    ""
+  },
+  { "a49a5e26a2f8cb63d05546c2a62f5343",
+    "907763b19b9b4ab6bd4f0281",
+    "",
+    "",
+    "a2be08210d8c470a8df6e8fbd79ec5cf",
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+const TestVector test_group_1[] = {
+  { "d1f6af919cde85661208bdce0c27cb22",
+    "898c6929b435017bf031c3c5",
+    "",
+    "7c5faa40e636bbc91107e68010c92b9f",
+    "ae45f11777540a2caeb128be8092468a",
+    NULL  // FAIL
+  },
+  { "2370e320d4344208e0ff5683f243b213",
+    "04dbb82f044d30831c441228",
+    "",
+    "d43a8e5089eea0d026c03a85178b27da",
+    "2a049c049d25aa95969b451d93c31c6e",
+    ""
+  },
+  { NULL }
+};
+
+const TestVector test_group_2[] = {
+  { "e98b72a9881a84ca6b76e0f43e68647a",
+    "8b23299fde174053f3d652ba",
+    "5a3c1cf1985dbb8bed818036fdd5ab42",
+    "",
+    "23c7ab0f952b7091cd324835043b5eb5",
+    "28286a321293253c3e0aa2704a278032"
+  },
+  { "33240636cd3236165f1a553b773e728e",
+    "17c4d61493ecdc8f31700b12",
+    "47bb7e23f7bdfe05a8091ac90e4f8b2e",
+    "",
+    "b723c70e931d9785f40fd4ab1d612dc9",
+    "95695a5b12f2870b9cc5fdc8f218a97d"
+  },
+  { "5164df856f1e9cac04a79b808dc5be39",
+    "e76925d5355e0584ce871b2b",
+    "0216c899c88d6e32c958c7e553daa5bc",
+    "",
+    "a145319896329c96df291f64efbe0e3a",
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+const TestVector test_group_3[] = {
+  { "af57f42c60c0fc5a09adb81ab86ca1c3",
+    "a2dc01871f37025dc0fc9a79",
+    "b9a535864f48ea7b6b1367914978f9bfa087d854bb0e269bed8d279d2eea1210e48947"
+    "338b22f9bad09093276a331e9c79c7f4",
+    "41dc38988945fcb44faf2ef72d0061289ef8efd8",
+    "4f71e72bde0018f555c5adcce062e005",
+    "3803a0727eeb0ade441e0ec107161ded2d425ec0d102f21f51bf2cf9947c7ec4aa7279"
+    "5b2f69b041596e8817d0a3c16f8fadeb"
+  },
+  { "ebc753e5422b377d3cb64b58ffa41b61",
+    "2e1821efaced9acf1f241c9b",
+    "069567190554e9ab2b50a4e1fbf9c147340a5025fdbd201929834eaf6532325899ccb9"
+    "f401823e04b05817243d2142a3589878",
+    "b9673412fd4f88ba0e920f46dd6438ff791d8eef",
+    "534d9234d2351cf30e565de47baece0b",
+    "39077edb35e9c5a4b1e4c2a6b9bb1fce77f00f5023af40333d6d699014c2bcf4209c18"
+    "353a18017f5b36bfc00b1f6dcb7ed485"
+  },
+  { "52bdbbf9cf477f187ec010589cb39d58",
+    "d3be36d3393134951d324b31",
+    "700188da144fa692cf46e4a8499510a53d90903c967f7f13e8a1bd8151a74adc4fe63e"
+    "32b992760b3a5f99e9a47838867000a9",
+    "93c4fc6a4135f54d640b0c976bf755a06a292c33",
+    "8ca4e38aa3dfa6b1d0297021ccf3ea5f",
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+const TestVector test_group_4[] = {
+  { "da2bb7d581493d692380c77105590201",
+    "44aa3e7856ca279d2eb020c6",
+    "9290d430c9e89c37f0446dbd620c9a6b34b1274aeb6f911f75867efcf95b6feda69f1a"
+    "f4ee16c761b3c9aeac3da03aa9889c88",
+    "4cd171b23bddb3a53cdf959d5c1710b481eb3785a90eb20a2345ee00d0bb7868c367ab"
+    "12e6f4dd1dee72af4eee1d197777d1d6499cc541f34edbf45cda6ef90b3c024f9272d7"
+    "2ec1909fb8fba7db88a4d6f7d3d925980f9f9f72",
+    "9e3ac938d3eb0cadd6f5c9e35d22ba38",
+    "9bbf4c1a2742f6ac80cb4e8a052e4a8f4f07c43602361355b717381edf9fabd4cb7e3a"
+    "d65dbd1378b196ac270588dd0621f642"
+  },
+  { "d74e4958717a9d5c0e235b76a926cae8",
+    "0b7471141e0c70b1995fd7b1",
+    "e701c57d2330bf066f9ff8cf3ca4343cafe4894651cd199bdaaa681ba486b4a65c5a22"
+    "b0f1420be29ea547d42c713bc6af66aa",
+    "4a42b7aae8c245c6f1598a395316e4b8484dbd6e64648d5e302021b1d3fa0a38f46e22"
+    "bd9c8080b863dc0016482538a8562a4bd0ba84edbe2697c76fd039527ac179ec5506cf"
+    "34a6039312774cedebf4961f3978b14a26509f96",
+    "e192c23cb036f0b31592989119eed55d",
+    "840d9fb95e32559fb3602e48590280a172ca36d9b49ab69510f5bd552bfab7a306f85f"
+    "f0a34bc305b88b804c60b90add594a17"
+  },
+  { "1986310c725ac94ecfe6422e75fc3ee7",
+    "93ec4214fa8e6dc4e3afc775",
+    "b178ec72f85a311ac4168f42a4b2c23113fbea4b85f4b9dabb74e143eb1b8b0a361e02"
+    "43edfd365b90d5b325950df0ada058f9",
+    "e80b88e62c49c958b5e0b8b54f532d9ff6aa84c8a40132e93e55b59fc24e8decf28463"
+    "139f155d1e8ce4ee76aaeefcd245baa0fc519f83a5fb9ad9aa40c4b21126013f576c42"
+    "72c2cb136c8fd091cc4539877a5d1e72d607f960",
+    "8b347853f11d75e81e8a95010be81f17",
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+const TestVector test_group_5[] = {
+  { "387218b246c1a8257748b56980e50c94",
+    "dd7e014198672be39f95b69d",
+    "cdba9e73eaf3d38eceb2b04a8d",
+    "",
+    "ecf90f4a47c9c626d6fb2c765d201556",
+    "48f5b426baca03064554cc2b30"
+  },
+  { "294de463721e359863887c820524b3d4",
+    "3338b35c9d57a5d28190e8c9",
+    "2f46634e74b8e4c89812ac83b9",
+    "",
+    "dabd506764e68b82a7e720aa18da0abe",
+    "46a2e55c8e264df211bd112685"
+  },
+  { "28ead7fd2179e0d12aa6d5d88c58c2dc",
+    "5055347f18b4d5add0ae5c41",
+    "142d8210c3fb84774cdbd0447a",
+    "",
+    "5fd321d9cdb01952dc85f034736c2a7d",
+    "3b95b981086ee73cc4d0cc1422"
+  },
+  { "7d7b6c988137b8d470c57bf674a09c87",
+    "9edf2aa970d016ac962e1fd8",
+    "a85b66c3cb5eab91d5bdc8bc0e",
+    "",
+    "dc054efc01f3afd21d9c2484819f569a",
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+const TestVector* const test_group_array[] = {
+  test_group_0,
+  test_group_1,
+  test_group_2,
+  test_group_3,
+  test_group_4,
+  test_group_5,
+};
+
+}  // namespace
+
+namespace net {
+namespace test {
+
+// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the plaintext.
+QuicData* DecryptWithNonce(Aes128Gcm12Decrypter* decrypter,
+                           StringPiece nonce,
+                           StringPiece associated_data,
+                           StringPiece ciphertext) {
+  size_t plaintext_size = ciphertext.length();
+  scoped_ptr<char[]> plaintext(new char[plaintext_size]);
+
+  if (!decrypter->Decrypt(nonce, associated_data, ciphertext,
+                          reinterpret_cast<unsigned char*>(plaintext.get()),
+                          &plaintext_size)) {
+    return nullptr;
+  }
+  return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+TEST(Aes128Gcm12DecrypterTest, Decrypt) {
+  for (size_t i = 0; i < arraysize(test_group_array); i++) {
+    SCOPED_TRACE(i);
+    const TestVector* test_vectors = test_group_array[i];
+    const TestGroupInfo& test_info = test_group_info[i];
+    for (size_t j = 0; test_vectors[j].key != nullptr; j++) {
+      // If not present then decryption is expected to fail.
+      bool has_pt = test_vectors[j].pt;
+
+      // Decode the test vector.
+      string key;
+      string iv;
+      string ct;
+      string aad;
+      string tag;
+      string pt;
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].key, &key));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].iv, &iv));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].ct, &ct));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].aad, &aad));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].tag, &tag));
+      if (has_pt) {
+        ASSERT_TRUE(DecodeHexString(test_vectors[j].pt, &pt));
+      }
+
+      // The test vector's lengths should look sane. Note that the lengths
+      // in |test_info| are in bits.
+      EXPECT_EQ(test_info.key_len, key.length() * 8);
+      EXPECT_EQ(test_info.iv_len, iv.length() * 8);
+      EXPECT_EQ(test_info.pt_len, ct.length() * 8);
+      EXPECT_EQ(test_info.aad_len, aad.length() * 8);
+      EXPECT_EQ(test_info.tag_len, tag.length() * 8);
+      if (has_pt) {
+        EXPECT_EQ(test_info.pt_len, pt.length() * 8);
+      }
+
+      // The test vectors have 16 byte authenticators but this code only uses
+      // the first 12.
+      ASSERT_LE(static_cast<size_t>(Aes128Gcm12Decrypter::kAuthTagSize),
+                tag.length());
+      tag.resize(Aes128Gcm12Decrypter::kAuthTagSize);
+      string ciphertext = ct + tag;
+
+      Aes128Gcm12Decrypter decrypter;
+      ASSERT_TRUE(decrypter.SetKey(key));
+
+      scoped_ptr<QuicData> decrypted(DecryptWithNonce(
+          &decrypter, iv,
+          // This deliberately tests that the decrypter can handle an AAD that
+          // is set to nullptr, as opposed to a zero-length, non-nullptr
+          // pointer.
+          aad.length() ? aad : StringPiece(), ciphertext));
+      if (!decrypted.get()) {
+        EXPECT_FALSE(has_pt);
+        continue;
+      }
+      EXPECT_TRUE(has_pt);
+
+      ASSERT_EQ(pt.length(), decrypted->length());
+      test::CompareCharArraysWithHexError("plaintext", decrypted->data(),
+                                          pt.length(), pt.data(), pt.length());
+    }
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_encrypter.h b/net/quic/crypto/aes_128_gcm_12_encrypter.h
new file mode 100644
index 0000000..1d8f321
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_encrypter.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
+
+#include "net/quic/crypto/aead_base_encrypter.h"
+
+namespace net {
+
+// An Aes128Gcm12Encrypter is a QuicEncrypter that implements the
+// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by
+// calling QuicEncrypter::Create(kAESG).
+//
+// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix
+// of the nonce is four bytes.
+class NET_EXPORT_PRIVATE Aes128Gcm12Encrypter : public AeadBaseEncrypter {
+ public:
+  enum {
+    // Authentication tags are truncated to 96 bits.
+    kAuthTagSize = 12,
+  };
+
+  Aes128Gcm12Encrypter();
+  virtual ~Aes128Gcm12Encrypter();
+
+#if !defined(USE_OPENSSL)
+ protected:
+  // AeadBaseEncrypter methods:
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const OVERRIDE;
+#endif
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Aes128Gcm12Encrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
diff --git a/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc b/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc
new file mode 100644
index 0000000..6214af5
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include <pk11pub.h>
+#include <secerr.h>
+
+#include "base/lazy_instance.h"
+#include "crypto/ghash.h"
+#include "crypto/scoped_nss_types.h"
+
+#if defined(USE_NSS)
+#include <dlfcn.h>
+#endif
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+
+// On Linux, dynamically link against the system version of libnss3.so. In
+// order to continue working on systems without up-to-date versions of NSS,
+// lookup PK11_Encrypt with dlsym.
+
+// GcmSupportChecker is a singleton which caches the results of runtime symbol
+// resolution of PK11_Encrypt.
+class GcmSupportChecker {
+ public:
+  static PK11_EncryptFunction pk11_encrypt_func() {
+    return pk11_encrypt_func_;
+  }
+
+ private:
+  friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>;
+
+  GcmSupportChecker() {
+#if !defined(USE_NSS)
+    // Using a bundled version of NSS that is guaranteed to have this symbol.
+    pk11_encrypt_func_ = PK11_Encrypt;
+#else
+    // Using system NSS libraries and PCKS #11 modules, which may not have the
+    // necessary function (PK11_Encrypt) or mechanism support (CKM_AES_GCM).
+
+    // If PK11_Encrypt() was successfully resolved, then NSS will support
+    // AES-GCM directly. This was introduced in NSS 3.15.
+    pk11_encrypt_func_ = (PK11_EncryptFunction)dlsym(RTLD_DEFAULT,
+                                                     "PK11_Encrypt");
+#endif
+  }
+
+  // |pk11_encrypt_func_| stores the runtime symbol resolution of PK11_Encrypt.
+  static PK11_EncryptFunction pk11_encrypt_func_;
+};
+
+// static
+PK11_EncryptFunction GcmSupportChecker::pk11_encrypt_func_ = nullptr;
+
+base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker =
+    LAZY_INSTANCE_INITIALIZER;
+
+// Calls PK11_Encrypt if it's available.  Otherwise, emulates CKM_AES_GCM using
+// CKM_AES_CTR and the GaloisHash class.
+SECStatus My_Encrypt(PK11SymKey* key,
+                     CK_MECHANISM_TYPE mechanism,
+                     SECItem* param,
+                     unsigned char* out,
+                     unsigned int* out_len,
+                     unsigned int max_len,
+                     const unsigned char* data,
+                     unsigned int data_len) {
+  // If PK11_Encrypt() was successfully resolved or if bundled version of NSS is
+  // being used, then NSS will support AES-GCM directly.
+  PK11_EncryptFunction pk11_encrypt_func =
+      GcmSupportChecker::pk11_encrypt_func();
+  if (pk11_encrypt_func != nullptr) {
+    return pk11_encrypt_func(key, mechanism, param, out, out_len, max_len, data,
+                             data_len);
+  }
+
+  // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x
+  // has a bug in the AES GCM code
+  // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing
+  // the PK11_Encrypt function
+  // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are
+  // resolved in NSS 3.15.
+
+  DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM));
+  DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS));
+
+  if (max_len < static_cast<unsigned int>(Aes128Gcm12Encrypter::kAuthTagSize)) {
+    DVLOG(1) << "max_len is less than kAuthTagSize";
+    PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+    return SECFailure;
+  }
+
+  const CK_GCM_PARAMS* gcm_params =
+      reinterpret_cast<CK_GCM_PARAMS*>(param->data);
+
+  DCHECK_EQ(gcm_params->ulTagBits,
+            static_cast<CK_ULONG>(Aes128Gcm12Encrypter::kAuthTagSize * 8));
+  if (gcm_params->ulIvLen != 12u) {
+    DVLOG(1) << "ulIvLen is not equal to 12";
+    PORT_SetError(SEC_ERROR_INPUT_LEN);
+    return SECFailure;
+  }
+
+  SECItem my_param = { siBuffer, nullptr, 0 };
+
+  // Step 1. Let H = CIPH_K(128 '0' bits).
+  unsigned char ghash_key[16] = {0};
+  crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey(
+      CKM_AES_ECB, CKA_ENCRYPT, key, &my_param));
+  if (!ctx) {
+    DVLOG(1) << "PK11_CreateContextBySymKey failed";
+    return SECFailure;
+  }
+  int output_len;
+  if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key),
+                    ghash_key, sizeof(ghash_key)) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+
+  PK11_Finalize(ctx.get());
+
+  if (output_len != sizeof(ghash_key)) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  // Step 2. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1.
+  CK_AES_CTR_PARAMS ctr_params = {0};
+  ctr_params.ulCounterBits = 32;
+  memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen);
+  ctr_params.cb[12] = 0;
+  ctr_params.cb[13] = 0;
+  ctr_params.cb[14] = 0;
+  ctr_params.cb[15] = 1;
+
+  my_param.type = siBuffer;
+  my_param.data = reinterpret_cast<unsigned char*>(&ctr_params);
+  my_param.len = sizeof(ctr_params);
+
+  ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key,
+                                       &my_param));
+  if (!ctx) {
+    DVLOG(1) << "PK11_CreateContextBySymKey failed";
+    return SECFailure;
+  }
+
+  // Step 6. Calculate the encryption mask of GCTR_K(J0, ...).
+  unsigned char tag_mask[16] = {0};
+  if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask),
+                    tag_mask, sizeof(tag_mask)) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+  if (output_len != sizeof(tag_mask)) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  // The const_cast for |data| can be removed if system NSS libraries are
+  // NSS 3.14.1 or later (NSS bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=808218).
+  if (PK11_CipherOp(ctx.get(), out, &output_len, max_len,
+                    const_cast<unsigned char*>(data), data_len) != SECSuccess) {
+    DVLOG(1) << "PK11_CipherOp failed";
+    return SECFailure;
+  }
+
+  PK11_Finalize(ctx.get());
+
+  if (static_cast<unsigned int>(output_len) != data_len) {
+    DVLOG(1) << "Wrong output length";
+    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+    return SECFailure;
+  }
+
+  if ((max_len - Aes128Gcm12Encrypter::kAuthTagSize) <
+      static_cast<unsigned int>(output_len)) {
+    DVLOG(1) << "(max_len - kAuthTagSize) is less than output_len";
+    PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+    return SECFailure;
+  }
+
+  crypto::GaloisHash ghash(ghash_key);
+  ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen);
+  ghash.UpdateCiphertext(out, output_len);
+  ghash.Finish(out + output_len, Aes128Gcm12Encrypter::kAuthTagSize);
+  for (unsigned int i = 0; i < Aes128Gcm12Encrypter::kAuthTagSize; i++) {
+    out[output_len + i] ^= tag_mask[i];
+  }
+
+  *out_len = output_len + Aes128Gcm12Encrypter::kAuthTagSize;
+  return SECSuccess;
+}
+
+}  // namespace
+
+Aes128Gcm12Encrypter::Aes128Gcm12Encrypter()
+    : AeadBaseEncrypter(CKM_AES_GCM, My_Encrypt, kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+  ignore_result(g_gcm_support_checker.Get());
+}
+
+Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {}
+
+void Aes128Gcm12Encrypter::FillAeadParams(StringPiece nonce,
+                                          StringPiece associated_data,
+                                          size_t auth_tag_size,
+                                          AeadParams* aead_params) const {
+  aead_params->len = sizeof(aead_params->data.gcm_params);
+  CK_GCM_PARAMS* gcm_params = &aead_params->data.gcm_params;
+  gcm_params->pIv =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+  gcm_params->ulIvLen = nonce.size();
+  gcm_params->pAAD =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+  gcm_params->ulAADLen = associated_data.size();
+  gcm_params->ulTagBits = auth_tag_size * 8;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc b/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc
new file mode 100644
index 0000000..6489528
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include <openssl/evp.h>
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+
+}  // namespace
+
+Aes128Gcm12Encrypter::Aes128Gcm12Encrypter()
+    : AeadBaseEncrypter(EVP_aead_aes_128_gcm(), kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {}
+
+}  // namespace net
diff --git a/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc b/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc
new file mode 100644
index 0000000..8ccaaed
--- /dev/null
+++ b/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc
@@ -0,0 +1,296 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The AES GCM test vectors come from the file gcmEncryptExtIV128.rsp
+// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on
+// 2013-02-01. The test vectors in that file look like this:
+//
+// [Keylen = 128]
+// [IVlen = 96]
+// [PTlen = 0]
+// [AADlen = 0]
+// [Taglen = 128]
+//
+// Count = 0
+// Key = 11754cd72aec309bf52f7687212e8957
+// IV = 3c819d9a9bed087615030b65
+// PT =
+// AAD =
+// CT =
+// Tag = 250327c674aaf477aef2675748cf6971
+//
+// Count = 1
+// Key = ca47248ac0b6f8372a97ac43508308ed
+// IV = ffd2b598feabc9019262d2be
+// PT =
+// AAD =
+// CT =
+// Tag = 60d20404af527d248d893ae495707d1a
+//
+// ...
+//
+// The gcmEncryptExtIV128.rsp file is huge (2.8 MB), so I selected just a
+// few test vectors for this unit test.
+
+// Describes a group of test vectors that all have a given key length, IV
+// length, plaintext length, AAD length, and tag length.
+struct TestGroupInfo {
+  size_t key_len;
+  size_t iv_len;
+  size_t pt_len;
+  size_t aad_len;
+  size_t tag_len;
+};
+
+// Each test vector consists of six strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+  const char* key;
+  const char* iv;
+  const char* pt;
+  const char* aad;
+  const char* ct;
+  const char* tag;
+};
+
+const TestGroupInfo test_group_info[] = {
+  { 128, 96, 0, 0, 128 },
+  { 128, 96, 0, 128, 128 },
+  { 128, 96, 128, 0, 128 },
+  { 128, 96, 408, 160, 128 },
+  { 128, 96, 408, 720, 128 },
+  { 128, 96, 104, 0, 128 },
+};
+
+const TestVector test_group_0[] = {
+  { "11754cd72aec309bf52f7687212e8957",
+    "3c819d9a9bed087615030b65",
+    "",
+    "",
+    "",
+    "250327c674aaf477aef2675748cf6971"
+  },
+  { "ca47248ac0b6f8372a97ac43508308ed",
+    "ffd2b598feabc9019262d2be",
+    "",
+    "",
+    "",
+    "60d20404af527d248d893ae495707d1a"
+  },
+  { NULL }
+};
+
+const TestVector test_group_1[] = {
+  { "77be63708971c4e240d1cb79e8d77feb",
+    "e0e00f19fed7ba0136a797f3",
+    "",
+    "7a43ec1d9c0a5a78a0b16533a6213cab",
+    "",
+    "209fcc8d3675ed938e9c7166709dd946"
+  },
+  { "7680c5d3ca6154758e510f4d25b98820",
+    "f8f105f9c3df4965780321f8",
+    "",
+    "c94c410194c765e3dcc7964379758ed3",
+    "",
+    "94dca8edfcf90bb74b153c8d48a17930"
+  },
+  { NULL }
+};
+
+const TestVector test_group_2[] = {
+  { "7fddb57453c241d03efbed3ac44e371c",
+    "ee283a3fc75575e33efd4887",
+    "d5de42b461646c255c87bd2962d3b9a2",
+    "",
+    "2ccda4a5415cb91e135c2a0f78c9b2fd",
+    "b36d1df9b9d5e596f83e8b7f52971cb3"
+  },
+  { "ab72c77b97cb5fe9a382d9fe81ffdbed",
+    "54cc7dc2c37ec006bcc6d1da",
+    "007c5e5b3e59df24a7c355584fc1518d",
+    "",
+    "0e1bde206a07a9c2c1b65300f8c64997",
+    "2b4401346697138c7a4891ee59867d0c"
+  },
+  { NULL }
+};
+
+const TestVector test_group_3[] = {
+  { "fe47fcce5fc32665d2ae399e4eec72ba",
+    "5adb9609dbaeb58cbd6e7275",
+    "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1"
+    "b840382c4bccaf3bafb4ca8429bea063",
+    "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a",
+    "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf539304373636525"
+    "3ddbc5db8778371495da76d269e5db3e",
+    "291ef1982e4defedaa2249f898556b47"
+  },
+  { "ec0c2ba17aa95cd6afffe949da9cc3a8",
+    "296bce5b50b7d66096d627ef",
+    "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987"
+    "b764b9611f6c0f8641843d5d58f3a242",
+    "f8d00f05d22bf68599bcdeb131292ad6e2df5d14",
+    "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a0716299"
+    "5506fde6309ffc19e716eddf1a828c5a",
+    "890147971946b627c40016da1ecf3e77"
+  },
+  { NULL }
+};
+
+const TestVector test_group_4[] = {
+  { "2c1f21cf0f6fb3661943155c3e3d8492",
+    "23cb5ff362e22426984d1907",
+    "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d6"
+    "8b5615ba7c1220ff6510e259f06655d8",
+    "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e"
+    "3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f"
+    "4488f33cfb5e979e42b6e1cfc0a60238982a7aec",
+    "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222"
+    "b6ad57af43e1895df9dca2a5344a62cc",
+    "57a3ee28136e94c74838997ae9823f3a"
+  },
+  { "d9f7d2411091f947b4d6f1e2d1f0fb2e",
+    "e1934f5db57cc983e6b180e7",
+    "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490"
+    "c2c6f6166f4a59431e182663fcaea05a",
+    "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d"
+    "0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a201"
+    "15d2e51398344b16bee1ed7c499b353d6c597af8",
+    "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d57"
+    "3c7891c2a91fbc48db29967ec9542b23",
+    "21b51ca862cb637cdd03b99a0f93b134"
+  },
+  { NULL }
+};
+
+const TestVector test_group_5[] = {
+  { "fe9bb47deb3a61e423c2231841cfd1fb",
+    "4d328eb776f500a2f7fb47aa",
+    "f1cc3818e421876bb6b8bbd6c9",
+    "",
+    "b88c5c1977b35b517b0aeae967",
+    "43fd4727fe5cdb4b5b42818dea7ef8c9"
+  },
+  { "6703df3701a7f54911ca72e24dca046a",
+    "12823ab601c350ea4bc2488c",
+    "793cd125b0b84a043e3ac67717",
+    "",
+    "b2051c80014f42f08735a7b0cd",
+    "38e6bcd29962e5f2c13626b85a877101"
+  },
+  { NULL }
+};
+
+const TestVector* const test_group_array[] = {
+  test_group_0,
+  test_group_1,
+  test_group_2,
+  test_group_3,
+  test_group_4,
+  test_group_5,
+};
+
+}  // namespace
+
+namespace net {
+namespace test {
+
+// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the ciphertext.
+QuicData* EncryptWithNonce(Aes128Gcm12Encrypter* encrypter,
+                           StringPiece nonce,
+                           StringPiece associated_data,
+                           StringPiece plaintext) {
+  size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length());
+  scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+  if (!encrypter->Encrypt(nonce, associated_data, plaintext,
+                          reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+    return nullptr;
+  }
+
+  return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+TEST(Aes128Gcm12EncrypterTest, Encrypt) {
+  for (size_t i = 0; i < arraysize(test_group_array); i++) {
+    SCOPED_TRACE(i);
+    const TestVector* test_vectors = test_group_array[i];
+    const TestGroupInfo& test_info = test_group_info[i];
+    for (size_t j = 0; test_vectors[j].key != nullptr; j++) {
+      // Decode the test vector.
+      string key;
+      string iv;
+      string pt;
+      string aad;
+      string ct;
+      string tag;
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].key, &key));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].iv, &iv));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].pt, &pt));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].aad, &aad));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].ct, &ct));
+      ASSERT_TRUE(DecodeHexString(test_vectors[j].tag, &tag));
+
+      // The test vector's lengths should look sane. Note that the lengths
+      // in |test_info| are in bits.
+      EXPECT_EQ(test_info.key_len, key.length() * 8);
+      EXPECT_EQ(test_info.iv_len, iv.length() * 8);
+      EXPECT_EQ(test_info.pt_len, pt.length() * 8);
+      EXPECT_EQ(test_info.aad_len, aad.length() * 8);
+      EXPECT_EQ(test_info.pt_len, ct.length() * 8);
+      EXPECT_EQ(test_info.tag_len, tag.length() * 8);
+
+      Aes128Gcm12Encrypter encrypter;
+      ASSERT_TRUE(encrypter.SetKey(key));
+      scoped_ptr<QuicData> encrypted(EncryptWithNonce(
+          &encrypter, iv,
+          // This deliberately tests that the encrypter can handle an AAD that
+          // is set to nullptr, as opposed to a zero-length, non-nullptr
+          // pointer.
+          aad.length() ? aad : StringPiece(), pt));
+      ASSERT_TRUE(encrypted.get());
+
+      // The test vectors have 16 byte authenticators but this code only uses
+      // the first 12.
+      ASSERT_LE(static_cast<size_t>(Aes128Gcm12Encrypter::kAuthTagSize),
+                tag.length());
+      tag.resize(Aes128Gcm12Encrypter::kAuthTagSize);
+
+      ASSERT_EQ(ct.length() + tag.length(), encrypted->length());
+      test::CompareCharArraysWithHexError("ciphertext", encrypted->data(),
+                                          ct.length(), ct.data(), ct.length());
+      test::CompareCharArraysWithHexError(
+          "authentication tag", encrypted->data() + ct.length(), tag.length(),
+          tag.data(), tag.length());
+    }
+  }
+}
+
+TEST(Aes128Gcm12EncrypterTest, GetMaxPlaintextSize) {
+  Aes128Gcm12Encrypter encrypter;
+  EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012));
+  EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112));
+  EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22));
+}
+
+TEST(Aes128Gcm12EncrypterTest, GetCiphertextSize) {
+  Aes128Gcm12Encrypter encrypter;
+  EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000));
+  EXPECT_EQ(112u, encrypter.GetCiphertextSize(100));
+  EXPECT_EQ(22u, encrypter.GetCiphertextSize(10));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/cert_compressor.cc b/net/quic/crypto/cert_compressor.cc
new file mode 100644
index 0000000..ba42152
--- /dev/null
+++ b/net/quic/crypto/cert_compressor.cc
@@ -0,0 +1,646 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/cert_compressor.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_utils.h"
+#include "third_party/zlib/zlib.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// kCommonCertSubstrings contains ~1500 bytes of common certificate substrings
+// in order to help zlib. This was generated via a fairly dumb algorithm from
+// the Alexa Top 5000 set - we could probably do better.
+static const unsigned char kCommonCertSubstrings[] = {
+  0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+  0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03,
+  0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30,
+  0x5f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01,
+  0x06, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6d, 0x01, 0x07,
+  0x17, 0x01, 0x30, 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+  0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x53, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x34,
+  0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31,
+  0x32, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72,
+  0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x2d, 0x61, 0x69, 0x61, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x45, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x2e, 0x63, 0x65,
+  0x72, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4a, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
+  0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x7b, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd2,
+  0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0xb4, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x30, 0x0b, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30,
+  0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74,
+  0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03,
+  0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33,
+  0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+  0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53,
+  0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55,
+  0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37,
+  0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x0c,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00,
+  0x30, 0x1d, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d,
+  0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86,
+  0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e,
+  0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x67, 0x64, 0x73, 0x31, 0x2d, 0x32, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74,
+  0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65,
+  0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+  0x70, 0x73, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17,
+  0x0d, 0x31, 0x33, 0x30, 0x35, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x73, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86,
+  0xf8, 0x45, 0x01, 0x07, 0x17, 0x06, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x53, 0x31, 0x17,
+  0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, 0x39,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, 0x73,
+  0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, 0x68,
+  0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76,
+  0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x31, 0x10, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x31, 0x13, 0x30, 0x11,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x47, 0x31, 0x13, 0x30, 0x11,
+  0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01,
+  0x03, 0x13, 0x02, 0x55, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x14, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0f, 0x13, 0x14, 0x50,
+  0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e,
+  0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x12, 0x31, 0x21, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x44, 0x6f, 0x6d, 0x61,
+  0x69, 0x6e, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x31, 0x14, 0x31, 0x31,
+  0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x53, 0x65, 0x65,
+  0x20, 0x77, 0x77, 0x77, 0x2e, 0x72, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x2e, 0x67, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+  0x69, 0x67, 0x6e, 0x31, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+  0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x64, 0x31, 0x1a,
+  0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x45, 0x56, 0x49, 0x6e, 0x74, 0x6c, 0x2d, 0x63, 0x63, 0x72,
+  0x74, 0x2e, 0x67, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x69, 0x63, 0x65, 0x72,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x6f, 0x63, 0x73, 0x70, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x30, 0x39, 0x72, 0x61, 0x70, 0x69, 0x64, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+  0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+  0x79, 0x2f, 0x30, 0x81, 0x80, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x01, 0x01, 0x04, 0x74, 0x30, 0x72, 0x30, 0x24, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64,
+  0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x4a, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64,
+  0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+  0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74,
+  0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72,
+  0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee,
+  0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x27,
+  0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x86, 0x30,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73,
+};
+
+// CertEntry represents a certificate in compressed form. Each entry is one of
+// the three types enumerated in |Type|.
+struct CertEntry {
+ public:
+  enum Type {
+    // Type 0 is reserved to mean "end of list" in the wire format.
+
+    // COMPRESSED means that the certificate is included in the trailing zlib
+    // data.
+    COMPRESSED = 1,
+    // CACHED means that the certificate is already known to the peer and will
+    // be replaced by its 64-bit hash (in |hash|).
+    CACHED = 2,
+    // COMMON means that the certificate is in a common certificate set known
+    // to the peer with hash |set_hash| and certificate index |index|.
+    COMMON = 3,
+  };
+
+  Type type;
+  uint64 hash;
+  uint64 set_hash;
+  uint32 index;
+};
+
+// MatchCerts returns a vector of CertEntries describing how to most
+// efficiently represent |certs| to a peer who has the common sets identified
+// by |client_common_set_hashes| and who has cached the certificates with the
+// 64-bit, FNV-1a hashes in |client_cached_cert_hashes|.
+vector<CertEntry> MatchCerts(const vector<string>& certs,
+                             StringPiece client_common_set_hashes,
+                             StringPiece client_cached_cert_hashes,
+                             const CommonCertSets* common_sets) {
+  vector<CertEntry> entries;
+  entries.reserve(certs.size());
+
+  const bool cached_valid =
+      client_cached_cert_hashes.size() % sizeof(uint64) == 0 &&
+      !client_cached_cert_hashes.empty();
+
+  for (vector<string>::const_iterator i = certs.begin();
+       i != certs.end(); ++i) {
+    CertEntry entry;
+
+    if (cached_valid) {
+      bool cached = false;
+
+      uint64 hash = QuicUtils::FNV1a_64_Hash(i->data(), i->size());
+      // This assumes that the machine is little-endian.
+      for (size_t i = 0; i < client_cached_cert_hashes.size();
+           i += sizeof(uint64)) {
+        uint64 cached_hash;
+        memcpy(&cached_hash, client_cached_cert_hashes.data() + i,
+               sizeof(uint64));
+        if (hash != cached_hash) {
+          continue;
+        }
+
+        entry.type = CertEntry::CACHED;
+        entry.hash = hash;
+        entries.push_back(entry);
+        cached = true;
+        break;
+      }
+
+      if (cached) {
+        continue;
+      }
+    }
+
+    if (common_sets && common_sets->MatchCert(*i, client_common_set_hashes,
+                                              &entry.set_hash, &entry.index)) {
+      entry.type = CertEntry::COMMON;
+      entries.push_back(entry);
+      continue;
+    }
+
+    entry.type = CertEntry::COMPRESSED;
+    entries.push_back(entry);
+  }
+
+  return entries;
+}
+
+// CertEntriesSize returns the size, in bytes, of the serialised form of
+// |entries|.
+size_t CertEntriesSize(const vector<CertEntry>& entries) {
+  size_t entries_size = 0;
+
+  for (vector<CertEntry>::const_iterator i = entries.begin();
+       i != entries.end(); ++i) {
+    entries_size++;
+    switch (i->type) {
+      case CertEntry::COMPRESSED:
+        break;
+      case CertEntry::CACHED:
+        entries_size += sizeof(uint64);
+        break;
+      case CertEntry::COMMON:
+        entries_size += sizeof(uint64) + sizeof(uint32);
+        break;
+    }
+  }
+
+  entries_size++;  // for end marker
+
+  return entries_size;
+}
+
+// SerializeCertEntries serialises |entries| to |out|, which must have enough
+// space to contain them.
+void SerializeCertEntries(uint8* out, const vector<CertEntry>& entries) {
+  for (vector<CertEntry>::const_iterator i = entries.begin();
+       i != entries.end(); ++i) {
+    *out++ = i->type;
+    switch (i->type) {
+      case CertEntry::COMPRESSED:
+        break;
+      case CertEntry::CACHED:
+        memcpy(out, &i->hash, sizeof(i->hash));
+        out += sizeof(uint64);
+        break;
+      case CertEntry::COMMON:
+        // Assumes a little-endian machine.
+        memcpy(out, &i->set_hash, sizeof(i->set_hash));
+        out += sizeof(i->set_hash);
+        memcpy(out, &i->index, sizeof(uint32));
+        out += sizeof(uint32);
+        break;
+    }
+  }
+
+  *out++ = 0;  // end marker
+}
+
+// ZlibDictForEntries returns a string that contains the zlib pre-shared
+// dictionary to use in order to decompress a zlib block following |entries|.
+// |certs| is one-to-one with |entries| and contains the certificates for those
+// entries that are CACHED or COMMON.
+string ZlibDictForEntries(const vector<CertEntry>& entries,
+                          const vector<string>& certs) {
+  string zlib_dict;
+
+  // The dictionary starts with the common and cached certs in reverse order.
+  size_t zlib_dict_size = 0;
+  for (size_t i = certs.size() - 1; i < certs.size(); i--) {
+    if (entries[i].type != CertEntry::COMPRESSED) {
+      zlib_dict_size += certs[i].size();
+    }
+  }
+
+  // At the end of the dictionary is a block of common certificate substrings.
+  zlib_dict_size += sizeof(kCommonCertSubstrings);
+
+  zlib_dict.reserve(zlib_dict_size);
+
+  for (size_t i = certs.size() - 1; i < certs.size(); i--) {
+    if (entries[i].type != CertEntry::COMPRESSED) {
+      zlib_dict += certs[i];
+    }
+  }
+
+  zlib_dict += string(reinterpret_cast<const char*>(kCommonCertSubstrings),
+                      sizeof(kCommonCertSubstrings));
+
+  DCHECK_EQ(zlib_dict.size(), zlib_dict_size);
+
+  return zlib_dict;
+}
+
+// HashCerts returns the FNV-1a hashes of |certs|.
+vector<uint64> HashCerts(const vector<string>& certs) {
+  vector<uint64> ret;
+  ret.reserve(certs.size());
+
+  for (vector<string>::const_iterator i = certs.begin();
+       i != certs.end(); ++i) {
+    ret.push_back(QuicUtils::FNV1a_64_Hash(i->data(), i->size()));
+  }
+
+  return ret;
+}
+
+// ParseEntries parses the serialised form of a vector of CertEntries from
+// |in_out| and writes them to |out_entries|. CACHED and COMMON entries are
+// resolved using |cached_certs| and |common_sets| and written to |out_certs|.
+// |in_out| is updated to contain the trailing data.
+bool ParseEntries(StringPiece* in_out,
+                  const vector<string>& cached_certs,
+                  const CommonCertSets* common_sets,
+                  vector<CertEntry>* out_entries,
+                  vector<string>* out_certs) {
+  StringPiece in = *in_out;
+  vector<uint64> cached_hashes;
+
+  out_entries->clear();
+  out_certs->clear();
+
+  for (;;) {
+    if (in.empty()) {
+      return false;
+    }
+    CertEntry entry;
+    const uint8 type_byte = in[0];
+    in.remove_prefix(1);
+
+    if (type_byte == 0) {
+      break;
+    }
+
+    entry.type = static_cast<CertEntry::Type>(type_byte);
+
+    switch (entry.type) {
+      case CertEntry::COMPRESSED:
+        out_certs->push_back(string());
+        break;
+      case CertEntry::CACHED: {
+        if (in.size() < sizeof(uint64)) {
+          return false;
+        }
+        memcpy(&entry.hash, in.data(), sizeof(uint64));
+        in.remove_prefix(sizeof(uint64));
+
+        if (cached_hashes.size() != cached_certs.size()) {
+          cached_hashes = HashCerts(cached_certs);
+        }
+        bool found = false;
+        for (size_t i = 0; i < cached_hashes.size(); i++) {
+          if (cached_hashes[i] == entry.hash) {
+            out_certs->push_back(cached_certs[i]);
+            found = true;
+            break;
+          }
+        }
+        if (!found) {
+          return false;
+        }
+        break;
+      }
+      case CertEntry::COMMON: {
+        if (!common_sets) {
+          return false;
+        }
+        if (in.size() < sizeof(uint64) + sizeof(uint32)) {
+          return false;
+        }
+        memcpy(&entry.set_hash, in.data(), sizeof(uint64));
+        in.remove_prefix(sizeof(uint64));
+        memcpy(&entry.index, in.data(), sizeof(uint32));
+        in.remove_prefix(sizeof(uint32));
+
+        StringPiece cert = common_sets->GetCert(entry.set_hash, entry.index);
+        if (cert.empty()) {
+          return false;
+        }
+        out_certs->push_back(cert.as_string());
+        break;
+      }
+      default:
+        return false;
+    }
+    out_entries->push_back(entry);
+  }
+
+  *in_out = in;
+  return true;
+}
+
+// ScopedZLib deals with the automatic destruction of a zlib context.
+class ScopedZLib {
+ public:
+  enum Type {
+    INFLATE,
+    DEFLATE,
+  };
+
+  explicit ScopedZLib(Type type) : z_(nullptr), type_(type) {}
+
+  void reset(z_stream* z) {
+    Clear();
+    z_ = z;
+  }
+
+  ~ScopedZLib() {
+    Clear();
+  }
+
+ private:
+  void Clear() {
+    if (!z_) {
+      return;
+    }
+
+    if (type_ == DEFLATE) {
+      deflateEnd(z_);
+    } else {
+      inflateEnd(z_);
+    }
+    z_ = nullptr;
+  }
+
+  z_stream* z_;
+  const Type type_;
+};
+
+}  // anonymous namespace
+
+
+// static
+string CertCompressor::CompressChain(const vector<string>& certs,
+                                     StringPiece client_common_set_hashes,
+                                     StringPiece client_cached_cert_hashes,
+                                     const CommonCertSets* common_sets) {
+  const vector<CertEntry> entries = MatchCerts(
+      certs, client_common_set_hashes, client_cached_cert_hashes, common_sets);
+  DCHECK_EQ(entries.size(), certs.size());
+
+  size_t uncompressed_size = 0;
+  for (size_t i = 0; i < entries.size(); i++) {
+    if (entries[i].type == CertEntry::COMPRESSED) {
+      uncompressed_size += 4 /* uint32 length */ + certs[i].size();
+    }
+  }
+
+  size_t compressed_size = 0;
+  z_stream z;
+  ScopedZLib scoped_z(ScopedZLib::DEFLATE);
+
+  if (uncompressed_size > 0) {
+    memset(&z, 0, sizeof(z));
+    int rv = deflateInit(&z, Z_DEFAULT_COMPRESSION);
+    DCHECK_EQ(Z_OK, rv);
+    if (rv != Z_OK) {
+      return "";
+    }
+    scoped_z.reset(&z);
+
+    string zlib_dict = ZlibDictForEntries(entries, certs);
+
+    rv = deflateSetDictionary(&z, reinterpret_cast<const uint8*>(&zlib_dict[0]),
+                              zlib_dict.size());
+    DCHECK_EQ(Z_OK, rv);
+    if (rv != Z_OK) {
+      return "";
+    }
+
+    compressed_size = deflateBound(&z, uncompressed_size);
+  }
+
+  const size_t entries_size = CertEntriesSize(entries);
+
+  string result;
+  result.resize(entries_size + (uncompressed_size > 0 ? 4 : 0) +
+                compressed_size);
+
+  uint8* j = reinterpret_cast<uint8*>(&result[0]);
+  SerializeCertEntries(j, entries);
+  j += entries_size;
+
+  if (uncompressed_size == 0) {
+    return result;
+  }
+
+  uint32 uncompressed_size_32 = uncompressed_size;
+  memcpy(j, &uncompressed_size_32, sizeof(uint32));
+  j += sizeof(uint32);
+
+  int rv;
+
+  z.next_out = j;
+  z.avail_out = compressed_size;
+
+  for (size_t i = 0; i < certs.size(); i++) {
+    if (entries[i].type != CertEntry::COMPRESSED) {
+      continue;
+    }
+
+    uint32 length32 = certs[i].size();
+    z.next_in = reinterpret_cast<uint8*>(&length32);
+    z.avail_in = sizeof(length32);
+    rv = deflate(&z, Z_NO_FLUSH);
+    DCHECK_EQ(Z_OK, rv);
+    DCHECK_EQ(0u, z.avail_in);
+    if (rv != Z_OK || z.avail_in) {
+      return "";
+    }
+
+    z.next_in =
+        const_cast<uint8*>(reinterpret_cast<const uint8*>(certs[i].data()));
+    z.avail_in = certs[i].size();
+    rv = deflate(&z, Z_NO_FLUSH);
+    DCHECK_EQ(Z_OK, rv);
+    DCHECK_EQ(0u, z.avail_in);
+    if (rv != Z_OK || z.avail_in) {
+      return "";
+    }
+  }
+
+  z.avail_in = 0;
+  rv = deflate(&z, Z_FINISH);
+  DCHECK_EQ(Z_STREAM_END, rv);
+  if (rv != Z_STREAM_END) {
+    return "";
+  }
+
+  result.resize(result.size() - z.avail_out);
+  return result;
+}
+
+// static
+bool CertCompressor::DecompressChain(StringPiece in,
+                                     const vector<string>& cached_certs,
+                                     const CommonCertSets* common_sets,
+                                     vector<string>* out_certs) {
+  vector<CertEntry> entries;
+  if (!ParseEntries(&in, cached_certs, common_sets, &entries, out_certs)) {
+    return false;
+  }
+  DCHECK_EQ(entries.size(), out_certs->size());
+
+  scoped_ptr<uint8[]> uncompressed_data;
+  StringPiece uncompressed;
+
+  if (!in.empty()) {
+    if (in.size() < sizeof(uint32)) {
+      return false;
+    }
+
+    uint32 uncompressed_size;
+    memcpy(&uncompressed_size, in.data(), sizeof(uncompressed_size));
+    in.remove_prefix(sizeof(uint32));
+
+    if (uncompressed_size > 128 * 1024) {
+      return false;
+    }
+
+    uncompressed_data.reset(new uint8[uncompressed_size]);
+    z_stream z;
+    ScopedZLib scoped_z(ScopedZLib::INFLATE);
+
+    memset(&z, 0, sizeof(z));
+    z.next_out = uncompressed_data.get();
+    z.avail_out = uncompressed_size;
+    z.next_in = const_cast<uint8*>(reinterpret_cast<const uint8*>(in.data()));
+    z.avail_in = in.size();
+
+    if (Z_OK != inflateInit(&z)) {
+      return false;
+    }
+    scoped_z.reset(&z);
+
+    int rv = inflate(&z, Z_FINISH);
+    if (rv == Z_NEED_DICT) {
+      string zlib_dict = ZlibDictForEntries(entries, *out_certs);
+      const uint8* dict = reinterpret_cast<const uint8*>(zlib_dict.data());
+      if (Z_OK != inflateSetDictionary(&z, dict, zlib_dict.size())) {
+        return false;
+      }
+      rv = inflate(&z, Z_FINISH);
+    }
+
+    if (Z_STREAM_END != rv || z.avail_out > 0 || z.avail_in > 0) {
+      return false;
+    }
+
+    uncompressed = StringPiece(reinterpret_cast<char*>(uncompressed_data.get()),
+                               uncompressed_size);
+  }
+
+  for (size_t i = 0; i < entries.size(); i++) {
+    switch (entries[i].type) {
+      case CertEntry::COMPRESSED:
+        if (uncompressed.size() < sizeof(uint32)) {
+          return false;
+        }
+        uint32 cert_len;
+        memcpy(&cert_len, uncompressed.data(), sizeof(cert_len));
+        uncompressed.remove_prefix(sizeof(uint32));
+        if (uncompressed.size() < cert_len) {
+          return false;
+        }
+        (*out_certs)[i] = uncompressed.substr(0, cert_len).as_string();
+        uncompressed.remove_prefix(cert_len);
+        break;
+      case CertEntry::CACHED:
+      case CertEntry::COMMON:
+        break;
+    }
+  }
+
+  if (!uncompressed.empty()) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/cert_compressor.h b/net/quic/crypto/cert_compressor.h
new file mode 100644
index 0000000..d95c5bc
--- /dev/null
+++ b/net/quic/crypto/cert_compressor.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CERT_COMPRESSOR_H_
+#define NET_QUIC_CRYPTO_CERT_COMPRESSOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// CertCompressor provides functions for compressing and decompressing
+// certificate chains using three techniquies:
+//   1) The peer may provide a list of a 64-bit, FNV-1a hashes of certificates
+//      that they already have. In the event that one of them is to be
+//      compressed, it can be replaced with just the hash.
+//   2) The peer may provide a number of hashes that represent sets of
+//      pre-shared certificates (CommonCertSets). If one of those certificates
+//      is to be compressed, and it's known to the given CommonCertSets, then it
+//      can be replaced with a set hash and certificate index.
+//   3) Otherwise the certificates are compressed with zlib using a pre-shared
+//      dictionary that consists of the certificates handled with the above
+//      methods and a small chunk of common substrings.
+class NET_EXPORT_PRIVATE CertCompressor {
+ public:
+  // CompressChain compresses the certificates in |certs| and returns a
+  // compressed representation. |common_sets| contains the common certificate
+  // sets known locally and |client_common_set_hashes| contains the hashes of
+  // the common sets known to the peer. |client_cached_cert_hashes| contains
+  // 64-bit, FNV-1a hashes of certificates that the peer already possesses.
+  static std::string CompressChain(const std::vector<std::string>& certs,
+                                   base::StringPiece client_common_set_hashes,
+                                   base::StringPiece client_cached_cert_hashes,
+                                   const CommonCertSets* common_sets);
+
+  // DecompressChain decompresses the result of |CompressChain|, given in |in|,
+  // into a series of certificates that are written to |out_certs|.
+  // |cached_certs| contains certificates that the peer may have omitted and
+  // |common_sets| contains the common certificate sets known locally.
+  static bool DecompressChain(base::StringPiece in,
+                              const std::vector<std::string>& cached_certs,
+                              const CommonCertSets* common_sets,
+                              std::vector<std::string>* out_certs);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CertCompressor);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CERT_COMPRESSOR_H_
diff --git a/net/quic/crypto/cert_compressor_test.cc b/net/quic/crypto/cert_compressor_test.cc
new file mode 100644
index 0000000..1b3c28a
--- /dev/null
+++ b/net/quic/crypto/cert_compressor_test.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/cert_compressor.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+TEST(CertCompressor, EmptyChain) {
+  vector<string> chain;
+  const string compressed = CertCompressor::CompressChain(
+      chain, StringPiece(), StringPiece(), nullptr);
+  EXPECT_EQ("00", base::HexEncode(compressed.data(), compressed.size()));
+
+  vector<string> chain2, cached_certs;
+  ASSERT_TRUE(CertCompressor::DecompressChain(compressed, cached_certs, nullptr,
+                                              &chain2));
+  EXPECT_EQ(chain.size(), chain2.size());
+}
+
+TEST(CertCompressor, Compressed) {
+  vector<string> chain;
+  chain.push_back("testcert");
+  const string compressed = CertCompressor::CompressChain(
+      chain, StringPiece(), StringPiece(), nullptr);
+  ASSERT_GE(compressed.size(), 2u);
+  EXPECT_EQ("0100", base::HexEncode(compressed.substr(0, 2).data(), 2));
+
+  vector<string> chain2, cached_certs;
+  ASSERT_TRUE(CertCompressor::DecompressChain(compressed, cached_certs, nullptr,
+                                              &chain2));
+  EXPECT_EQ(chain.size(), chain2.size());
+  EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, Common) {
+  vector<string> chain;
+  chain.push_back("testcert");
+  static const uint64 set_hash = 42;
+  scoped_ptr<CommonCertSets> common_sets(
+      CryptoTestUtils::MockCommonCertSets(chain[0], set_hash, 1));
+  const string compressed = CertCompressor::CompressChain(
+      chain,
+      StringPiece(reinterpret_cast<const char*>(&set_hash), sizeof(set_hash)),
+      StringPiece(), common_sets.get());
+  const string common("03"               /* common */
+                      "2A00000000000000" /* set hash 42 */
+                      "01000000"         /* index 1 */
+                      "00"               /* end of list */);
+  EXPECT_EQ(common.data(),
+            base::HexEncode(compressed.data(), compressed.size()));
+
+  vector<string> chain2, cached_certs;
+  ASSERT_TRUE(CertCompressor::DecompressChain(compressed, cached_certs,
+                                              common_sets.get(), &chain2));
+  EXPECT_EQ(chain.size(), chain2.size());
+  EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, Cached) {
+  vector<string> chain;
+  chain.push_back("testcert");
+  uint64 hash = QuicUtils::FNV1a_64_Hash(chain[0].data(), chain[0].size());
+  StringPiece hash_bytes(reinterpret_cast<char*>(&hash), sizeof(hash));
+  const string compressed =
+      CertCompressor::CompressChain(chain, StringPiece(), hash_bytes, nullptr);
+
+  EXPECT_EQ("02" /* cached */ +
+            base::HexEncode(hash_bytes.data(), hash_bytes.size()) +
+            "00" /* end of list */,
+            base::HexEncode(compressed.data(), compressed.size()));
+
+  vector<string> cached_certs, chain2;
+  cached_certs.push_back(chain[0]);
+  ASSERT_TRUE(CertCompressor::DecompressChain(compressed, cached_certs, nullptr,
+                                              &chain2));
+  EXPECT_EQ(chain.size(), chain2.size());
+  EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, BadInputs) {
+  vector<string> cached_certs, chain;
+
+  /* bad entry type */
+  const string bad_entry("04");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(bad_entry.data(), bad_entry.size()),
+      cached_certs, nullptr, &chain));
+
+  /* no terminator */
+  const string no_terminator("01");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(no_terminator.data(), no_terminator.size()),
+      cached_certs, nullptr, &chain));
+
+  /* hash truncated */
+  const string hash_truncated("0200");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(hash_truncated.data(), hash_truncated.size()),
+      cached_certs, nullptr, &chain));
+
+  /* hash and index truncated */
+  const string hash_and_index_truncated("0300");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(hash_and_index_truncated.data(),
+                      hash_and_index_truncated.size()),
+      cached_certs, nullptr, &chain));
+
+  /* without a CommonCertSets */
+  const string without_a_common_cert_set(
+      "03" "0000000000000000" "00000000");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(without_a_common_cert_set.data(),
+                      without_a_common_cert_set.size()),
+      cached_certs, nullptr, &chain));
+
+  scoped_ptr<CommonCertSets> common_sets(
+      CryptoTestUtils::MockCommonCertSets("foo", 42, 1));
+
+  /* incorrect hash and index */
+  const string incorrect_hash_and_index(
+      "03" "a200000000000000" "00000000");
+  EXPECT_FALSE(CertCompressor::DecompressChain(
+      base::HexEncode(incorrect_hash_and_index.data(),
+                      incorrect_hash_and_index.size()),
+      cached_certs, nullptr, &chain));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_decrypter.h b/net/quic/crypto/chacha20_poly1305_decrypter.h
new file mode 100644
index 0000000..9d24ba2
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_decrypter.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
+
+#include "net/quic/crypto/aead_base_decrypter.h"
+
+namespace net {
+
+// A ChaCha20Poly1305Decrypter is a QuicDecrypter that implements the
+// AEAD_CHACHA20_POLY1305 algorithm specified in
+// draft-agl-tls-chacha20poly1305-04, except that it truncates the Poly1305
+// authenticator to 12 bytes. Create an instance by calling
+// QuicDecrypter::Create(kCC12).
+//
+// It uses an authentication tag of 16 bytes (128 bits). There is no
+// fixed nonce prefix.
+class NET_EXPORT_PRIVATE ChaCha20Poly1305Decrypter : public AeadBaseDecrypter {
+ public:
+  enum {
+    kAuthTagSize = 12,
+  };
+
+  ChaCha20Poly1305Decrypter();
+  virtual ~ChaCha20Poly1305Decrypter();
+
+  // Returns true if the underlying crypto library supports ChaCha20+Poly1305.
+  static bool IsSupported();
+
+#if !defined(USE_OPENSSL)
+ protected:
+  // AeadBaseDecrypter methods:
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const OVERRIDE;
+#endif
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChaCha20Poly1305Decrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CHACHA20_POLY1305_DECRYPTER_H_
diff --git a/net/quic/crypto/chacha20_poly1305_decrypter_nss.cc b/net/quic/crypto/chacha20_poly1305_decrypter_nss.cc
new file mode 100644
index 0000000..fbeaaeb
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_decrypter_nss.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_decrypter.h"
+
+#include <pk11pub.h>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 32;
+const size_t kNoncePrefixSize = 0;
+
+}  // namespace
+
+#if defined(USE_NSS)
+
+// System NSS doesn't support ChaCha20+Poly1305 yet.
+
+ChaCha20Poly1305Decrypter::ChaCha20Poly1305Decrypter()
+    : AeadBaseDecrypter(CKM_INVALID_MECHANISM, nullptr, kKeySize,
+                        kAuthTagSize, kNoncePrefixSize) {
+  NOTIMPLEMENTED();
+}
+
+ChaCha20Poly1305Decrypter::~ChaCha20Poly1305Decrypter() {}
+
+// static
+bool ChaCha20Poly1305Decrypter::IsSupported() {
+  return false;
+}
+
+void ChaCha20Poly1305Decrypter::FillAeadParams(StringPiece nonce,
+                                               StringPiece associated_data,
+                                               size_t auth_tag_size,
+                                               AeadParams* aead_params) const {
+  NOTIMPLEMENTED();
+}
+
+#else  // defined(USE_NSS)
+
+ChaCha20Poly1305Decrypter::ChaCha20Poly1305Decrypter()
+    : AeadBaseDecrypter(CKM_NSS_CHACHA20_POLY1305, PK11_Decrypt, kKeySize,
+                        kAuthTagSize, kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+ChaCha20Poly1305Decrypter::~ChaCha20Poly1305Decrypter() {}
+
+// static
+bool ChaCha20Poly1305Decrypter::IsSupported() {
+  return true;
+}
+
+void ChaCha20Poly1305Decrypter::FillAeadParams(StringPiece nonce,
+                                               StringPiece associated_data,
+                                               size_t auth_tag_size,
+                                               AeadParams* aead_params) const {
+  aead_params->len = sizeof(aead_params->data.nss_aead_params);
+  CK_NSS_AEAD_PARAMS* nss_aead_params = &aead_params->data.nss_aead_params;
+  nss_aead_params->pIv =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+  nss_aead_params->ulIvLen = nonce.size();
+  nss_aead_params->pAAD =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+  nss_aead_params->ulAADLen = associated_data.size();
+  nss_aead_params->ulTagLen = auth_tag_size;
+}
+
+#endif  // defined(USE_NSS)
+
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_decrypter_openssl.cc b/net/quic/crypto/chacha20_poly1305_decrypter_openssl.cc
new file mode 100644
index 0000000..7f0e24d
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_decrypter_openssl.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_decrypter.h"
+
+#include <openssl/evp.h>
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 32;
+const size_t kNoncePrefixSize = 0;
+
+}  // namespace
+
+ChaCha20Poly1305Decrypter::ChaCha20Poly1305Decrypter()
+    : AeadBaseDecrypter(EVP_aead_chacha20_poly1305(), kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+ChaCha20Poly1305Decrypter::~ChaCha20Poly1305Decrypter() {}
+
+// static
+bool ChaCha20Poly1305Decrypter::IsSupported() { return true; }
+
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_decrypter_test.cc b/net/quic/crypto/chacha20_poly1305_decrypter_test.cc
new file mode 100644
index 0000000..e83acc0
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_decrypter_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_decrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The test vectors come from draft-agl-tls-chacha20poly1305-04 Section 7.
+
+// Each test vector consists of six strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+  // Input:
+  const char* key;
+  const char* iv;
+  const char* aad;
+  const char* ct;
+
+  // Expected output:
+  const char* pt;  // An empty string "" means decryption succeeded and
+                   // the plaintext is zero-length. NULL means decryption
+                   // failed.
+};
+
+const TestVector test_vectors[] = {
+  { "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110"
+        "0a1007",
+    "cd7cf67be39c794a",
+    "87e229d4500845a079c0",
+    "e3e446f7ede9a19b62a4677dabf4e3d24b876bb28475",  // "3896e1d6" truncated.
+    "86d09974840bded2a5ca"
+  },
+  // Modify the ciphertext (ChaCha20 encryption output).
+  { "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110"
+        "0a1007",
+    "cd7cf67be39c794a",
+    "87e229d4500845a079c0",
+    "f3e446f7ede9a19b62a4677dabf4e3d24b876bb28475",  // "3896e1d6" truncated.
+    NULL  // FAIL
+  },
+  // Modify the ciphertext (Poly1305 authenticator).
+  { "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110"
+        "0a1007",
+    "cd7cf67be39c794a",
+    "87e229d4500845a079c0",
+    "e3e446f7ede9a19b62a4677dabf4e3d24b876bb28476",  // "3896e1d6" truncated.
+    NULL  // FAIL
+  },
+  // Modify the associated data.
+  { "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110"
+        "0a1007",
+    "dd7cf67be39c794a",
+    "87e229d4500845a079c0",
+    "e3e446f7ede9a19b62a4677dabf4e3d24b876bb28475",  // "3896e1d6" truncated.
+    NULL  // FAIL
+  },
+  { NULL }
+};
+
+}  // namespace
+
+namespace net {
+namespace test {
+
+// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the plaintext.
+QuicData* DecryptWithNonce(ChaCha20Poly1305Decrypter* decrypter,
+                           StringPiece nonce,
+                           StringPiece associated_data,
+                           StringPiece ciphertext) {
+  size_t plaintext_size = ciphertext.length();
+  scoped_ptr<char[]> plaintext(new char[plaintext_size]);
+
+  if (!decrypter->Decrypt(nonce, associated_data, ciphertext,
+                          reinterpret_cast<unsigned char*>(plaintext.get()),
+                          &plaintext_size)) {
+    return nullptr;
+  }
+  return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+TEST(ChaCha20Poly1305DecrypterTest, Decrypt) {
+  if (!ChaCha20Poly1305Decrypter::IsSupported()) {
+    LOG(INFO) << "ChaCha20+Poly1305 not supported. Test skipped.";
+    return;
+  }
+
+  for (size_t i = 0; test_vectors[i].key != nullptr; i++) {
+    // If not present then decryption is expected to fail.
+    bool has_pt = test_vectors[i].pt;
+
+    // Decode the test vector.
+    string key;
+    string iv;
+    string aad;
+    string ct;
+    string pt;
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].key, &key));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].iv, &iv));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].aad, &aad));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].ct, &ct));
+    if (has_pt) {
+      ASSERT_TRUE(DecodeHexString(test_vectors[i].pt, &pt));
+    }
+
+    ChaCha20Poly1305Decrypter decrypter;
+    ASSERT_TRUE(decrypter.SetKey(key));
+    scoped_ptr<QuicData> decrypted(DecryptWithNonce(
+        &decrypter, iv,
+        // This deliberately tests that the decrypter can handle an AAD that
+        // is set to nullptr, as opposed to a zero-length, non-nullptr pointer.
+        StringPiece(aad.length() ? aad.data() : nullptr, aad.length()), ct));
+    if (!decrypted.get()) {
+      EXPECT_FALSE(has_pt);
+      continue;
+    }
+    EXPECT_TRUE(has_pt);
+
+    ASSERT_EQ(pt.length(), decrypted->length());
+    test::CompareCharArraysWithHexError("plaintext", decrypted->data(),
+                                        pt.length(), pt.data(), pt.length());
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_encrypter.h b/net/quic/crypto/chacha20_poly1305_encrypter.h
new file mode 100644
index 0000000..4a68caa
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_encrypter.h
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
+
+#include "net/quic/crypto/aead_base_encrypter.h"
+
+namespace net {
+
+// A ChaCha20Poly1305Encrypter is a QuicEncrypter that implements the
+// AEAD_CHACHA20_POLY1305 algorithm specified in
+// draft-agl-tls-chacha20poly1305-04, except that it truncates the Poly1305
+// authenticator to 12 bytes. Create an instance by calling
+// QuicEncrypter::Create(kCC12).
+//
+// It uses an authentication tag of 16 bytes (128 bits). There is no
+// fixed nonce prefix.
+class NET_EXPORT_PRIVATE ChaCha20Poly1305Encrypter : public AeadBaseEncrypter {
+ public:
+  enum {
+    kAuthTagSize = 12,
+  };
+
+  ChaCha20Poly1305Encrypter();
+  virtual ~ChaCha20Poly1305Encrypter();
+
+  // Returns true if the underlying crypto library supports ChaCha20+Poly1305.
+  static bool IsSupported();
+
+#if !defined(USE_OPENSSL)
+ protected:
+  // AeadBaseEncrypter methods:
+  virtual void FillAeadParams(base::StringPiece nonce,
+                              base::StringPiece associated_data,
+                              size_t auth_tag_size,
+                              AeadParams* aead_params) const OVERRIDE;
+#endif
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChaCha20Poly1305Encrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CHACHA20_POLY1305_ENCRYPTER_H_
diff --git a/net/quic/crypto/chacha20_poly1305_encrypter_nss.cc b/net/quic/crypto/chacha20_poly1305_encrypter_nss.cc
new file mode 100644
index 0000000..40cd896
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_encrypter_nss.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+
+#include <pk11pub.h>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 32;
+const size_t kNoncePrefixSize = 0;
+
+}  // namespace
+
+#if defined(USE_NSS)
+
+// System NSS doesn't support ChaCha20+Poly1305 yet.
+
+ChaCha20Poly1305Encrypter::ChaCha20Poly1305Encrypter()
+    : AeadBaseEncrypter(CKM_INVALID_MECHANISM, nullptr, kKeySize,
+                        kAuthTagSize, kNoncePrefixSize) {
+  NOTIMPLEMENTED();
+}
+
+ChaCha20Poly1305Encrypter::~ChaCha20Poly1305Encrypter() {}
+
+// static
+bool ChaCha20Poly1305Encrypter::IsSupported() {
+  return false;
+}
+
+void ChaCha20Poly1305Encrypter::FillAeadParams(StringPiece nonce,
+                                               StringPiece associated_data,
+                                               size_t auth_tag_size,
+                                               AeadParams* aead_params) const {
+  NOTIMPLEMENTED();
+}
+
+#else  // defined(USE_NSS)
+
+ChaCha20Poly1305Encrypter::ChaCha20Poly1305Encrypter()
+    : AeadBaseEncrypter(CKM_NSS_CHACHA20_POLY1305, PK11_Encrypt, kKeySize,
+                        kAuthTagSize, kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+ChaCha20Poly1305Encrypter::~ChaCha20Poly1305Encrypter() {}
+
+// static
+bool ChaCha20Poly1305Encrypter::IsSupported() {
+  return true;
+}
+
+void ChaCha20Poly1305Encrypter::FillAeadParams(StringPiece nonce,
+                                               StringPiece associated_data,
+                                               size_t auth_tag_size,
+                                               AeadParams* aead_params) const {
+  aead_params->len = sizeof(aead_params->data.nss_aead_params);
+  CK_NSS_AEAD_PARAMS* nss_aead_params = &aead_params->data.nss_aead_params;
+  nss_aead_params->pIv =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+  nss_aead_params->ulIvLen = nonce.size();
+  nss_aead_params->pAAD =
+      reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+  nss_aead_params->ulAADLen = associated_data.size();
+  nss_aead_params->ulTagLen = auth_tag_size;
+}
+
+#endif  // defined(USE_NSS)
+
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_encrypter_openssl.cc b/net/quic/crypto/chacha20_poly1305_encrypter_openssl.cc
new file mode 100644
index 0000000..e256c2a
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_encrypter_openssl.cc
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+
+#include <openssl/evp.h>
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 32;
+const size_t kNoncePrefixSize = 0;
+
+}  // namespace
+
+ChaCha20Poly1305Encrypter::ChaCha20Poly1305Encrypter()
+    : AeadBaseEncrypter(EVP_aead_chacha20_poly1305(), kKeySize, kAuthTagSize,
+                        kNoncePrefixSize) {
+  COMPILE_ASSERT(kKeySize <= kMaxKeySize, key_size_too_big);
+  COMPILE_ASSERT(kNoncePrefixSize <= kMaxNoncePrefixSize,
+                 nonce_prefix_size_too_big);
+}
+
+ChaCha20Poly1305Encrypter::~ChaCha20Poly1305Encrypter() {}
+
+// static
+bool ChaCha20Poly1305Encrypter::IsSupported() { return true; }
+
+}  // namespace net
diff --git a/net/quic/crypto/chacha20_poly1305_encrypter_test.cc b/net/quic/crypto/chacha20_poly1305_encrypter_test.cc
new file mode 100644
index 0000000..4f33337
--- /dev/null
+++ b/net/quic/crypto/chacha20_poly1305_encrypter_test.cc
@@ -0,0 +1,108 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The test vectors come from draft-agl-tls-chacha20poly1305-04 Section 7.
+
+// Each test vector consists of five strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+  const char* key;
+  const char* pt;
+  const char* iv;
+  const char* aad;
+  const char* ct;
+};
+
+const TestVector test_vectors[] = {
+  { "4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110"
+        "0a1007",
+    "86d09974840bded2a5ca",
+    "cd7cf67be39c794a",
+    "87e229d4500845a079c0",
+    "e3e446f7ede9a19b62a4677dabf4e3d24b876bb28475"  // "3896e1d6" truncated.
+  },
+  { NULL }
+};
+
+}  // namespace
+
+namespace net {
+namespace test {
+
+// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the ciphertext.
+QuicData* EncryptWithNonce(ChaCha20Poly1305Encrypter* encrypter,
+                           StringPiece nonce,
+                           StringPiece associated_data,
+                           StringPiece plaintext) {
+  size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length());
+  scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+  if (!encrypter->Encrypt(nonce, associated_data, plaintext,
+                          reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+    return nullptr;
+  }
+
+  return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+TEST(ChaCha20Poly1305EncrypterTest, Encrypt) {
+  if (!ChaCha20Poly1305Encrypter::IsSupported()) {
+    LOG(INFO) << "ChaCha20+Poly1305 not supported. Test skipped.";
+    return;
+  }
+
+  for (size_t i = 0; test_vectors[i].key != nullptr; i++) {
+    // Decode the test vector.
+    string key;
+    string pt;
+    string iv;
+    string aad;
+    string ct;
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].key, &key));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].pt, &pt));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].iv, &iv));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].aad, &aad));
+    ASSERT_TRUE(DecodeHexString(test_vectors[i].ct, &ct));
+
+    ChaCha20Poly1305Encrypter encrypter;
+    ASSERT_TRUE(encrypter.SetKey(key));
+    scoped_ptr<QuicData> encrypted(EncryptWithNonce(
+        &encrypter, iv,
+        // This deliberately tests that the encrypter can handle an AAD that
+        // is set to nullptr, as opposed to a zero-length, non-nullptr pointer.
+        StringPiece(aad.length() ? aad.data() : nullptr, aad.length()), pt));
+    ASSERT_TRUE(encrypted.get());
+
+    test::CompareCharArraysWithHexError("ciphertext", encrypted->data(),
+                                        encrypted->length(), ct.data(),
+                                        ct.length());
+  }
+}
+
+TEST(ChaCha20Poly1305EncrypterTest, GetMaxPlaintextSize) {
+  ChaCha20Poly1305Encrypter encrypter;
+  EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012));
+  EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112));
+  EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22));
+}
+
+TEST(ChaCha20Poly1305EncrypterTest, GetCiphertextSize) {
+  ChaCha20Poly1305Encrypter encrypter;
+  EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000));
+  EXPECT_EQ(112u, encrypter.GetCiphertextSize(100));
+  EXPECT_EQ(22u, encrypter.GetCiphertextSize(10));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/channel_id.cc b/net/quic/crypto/channel_id.cc
new file mode 100644
index 0000000..e707bf0
--- /dev/null
+++ b/net/quic/crypto/channel_id.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/channel_id.h"
+
+namespace net {
+
+// static
+const char ChannelIDVerifier::kContextStr[] = "QUIC ChannelID";
+// static
+const char ChannelIDVerifier::kClientToServerStr[] = "client -> server";
+
+}  // namespace net
diff --git a/net/quic/crypto/channel_id.h b/net/quic/crypto/channel_id.h
new file mode 100644
index 0000000..bd335fb
--- /dev/null
+++ b/net/quic/crypto/channel_id.h
@@ -0,0 +1,98 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CHANNEL_ID_H_
+#define NET_QUIC_CRYPTO_CHANNEL_ID_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_types.h"
+
+namespace net {
+
+// ChannelIDKey is an interface that supports signing with and serializing a
+// ChannelID key.
+class NET_EXPORT_PRIVATE ChannelIDKey {
+ public:
+  virtual ~ChannelIDKey() {}
+
+  // Sign signs |signed_data| using the ChannelID private key and puts the
+  // signature into |out_signature|. It returns true on success.
+  virtual bool Sign(base::StringPiece signed_data,
+                    std::string* out_signature) const = 0;
+
+  // SerializeKey returns the serialized ChannelID public key.
+  virtual std::string SerializeKey() const = 0;
+};
+
+// ChannelIDSourceCallback provides a generic mechanism for a ChannelIDSource
+// to call back after an asynchronous GetChannelIDKey operation.
+class ChannelIDSourceCallback {
+ public:
+  virtual ~ChannelIDSourceCallback() {}
+
+  // Run is called on the original thread to mark the completion of an
+  // asynchonous GetChannelIDKey operation. If |*channel_id_key| is not nullptr
+  // then the channel ID lookup is successful. |Run| may take ownership of
+  // |*channel_id_key| by calling |release| on it.
+  virtual void Run(scoped_ptr<ChannelIDKey>* channel_id_key) = 0;
+};
+
+// ChannelIDSource is an abstract interface by which a QUIC client can obtain
+// a ChannelIDKey for a given hostname.
+class NET_EXPORT_PRIVATE ChannelIDSource {
+ public:
+  virtual ~ChannelIDSource() {}
+
+  // GetChannelIDKey looks up the ChannelIDKey for |hostname|. On success it
+  // returns QUIC_SUCCESS and stores the ChannelIDKey in |*channel_id_key|,
+  // which the caller takes ownership of. On failure, it returns QUIC_FAILURE.
+  //
+  // This function may also return QUIC_PENDING, in which case the
+  // ChannelIDSource will call back, on the original thread, via |callback|
+  // when complete. In this case, the ChannelIDSource will take ownership of
+  // |callback|.
+  virtual QuicAsyncStatus GetChannelIDKey(
+      const std::string& hostname,
+      scoped_ptr<ChannelIDKey>* channel_id_key,
+      ChannelIDSourceCallback* callback) = 0;
+};
+
+// ChannelIDVerifier verifies ChannelID signatures.
+class NET_EXPORT_PRIVATE ChannelIDVerifier {
+ public:
+  // kContextStr is prepended to the data to be signed in order to ensure that
+  // a ChannelID signature cannot be used in a different context. (The
+  // terminating NUL byte is inclued.)
+  static const char kContextStr[];
+  // kClientToServerStr follows kContextStr to specify that the ChannelID is
+  // being used in the client to server direction. (The terminating NUL byte is
+  // included.)
+  static const char kClientToServerStr[];
+
+  // Verify returns true iff |signature| is a valid signature of |signed_data|
+  // by |key|.
+  static bool Verify(base::StringPiece key,
+                     base::StringPiece signed_data,
+                     base::StringPiece signature);
+
+  // FOR TESTING ONLY: VerifyRaw returns true iff |signature| is a valid
+  // signature of |signed_data| by |key|. |is_channel_id_signature| indicates
+  // whether |signature| is a ChannelID signature (with kContextStr prepended
+  // to the data to be signed).
+  static bool VerifyRaw(base::StringPiece key,
+                        base::StringPiece signed_data,
+                        base::StringPiece signature,
+                        bool is_channel_id_signature);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChannelIDVerifier);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CHANNEL_ID_H_
diff --git a/net/quic/crypto/channel_id_chromium.cc b/net/quic/crypto/channel_id_chromium.cc
new file mode 100644
index 0000000..53feed3
--- /dev/null
+++ b/net/quic/crypto/channel_id_chromium.cc
@@ -0,0 +1,249 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/channel_id_chromium.h"
+
+#include <string>
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/ssl/channel_id_service.h"
+
+namespace net {
+
+ChannelIDKeyChromium::ChannelIDKeyChromium(
+    crypto::ECPrivateKey* ec_private_key)
+    : ec_private_key_(ec_private_key) {}
+
+ChannelIDKeyChromium::~ChannelIDKeyChromium() {}
+
+bool ChannelIDKeyChromium::Sign(base::StringPiece signed_data,
+                                std::string* out_signature) const {
+  scoped_ptr<crypto::ECSignatureCreator> sig_creator(
+      crypto::ECSignatureCreator::Create(ec_private_key_.get()));
+  if (!sig_creator) {
+    return false;
+  }
+  const size_t len1 = strlen(ChannelIDVerifier::kContextStr) + 1;
+  const size_t len2 = strlen(ChannelIDVerifier::kClientToServerStr) + 1;
+  std::vector<uint8> data(len1 + len2 + signed_data.size());
+  memcpy(&data[0], ChannelIDVerifier::kContextStr, len1);
+  memcpy(&data[len1], ChannelIDVerifier::kClientToServerStr, len2);
+  memcpy(&data[len1 + len2], signed_data.data(), signed_data.size());
+  std::vector<uint8> der_signature;
+  if (!sig_creator->Sign(&data[0], data.size(), &der_signature)) {
+    return false;
+  }
+  std::vector<uint8> raw_signature;
+  if (!sig_creator->DecodeSignature(der_signature, &raw_signature)) {
+    return false;
+  }
+  memcpy(WriteInto(out_signature, raw_signature.size() + 1),
+         &raw_signature[0], raw_signature.size());
+  return true;
+}
+
+std::string ChannelIDKeyChromium::SerializeKey() const {
+  std::string out_key;
+  if (!ec_private_key_->ExportRawPublicKey(&out_key)) {
+    return std::string();
+  }
+  return out_key;
+}
+
+// A Job handles the lookup of a single channel ID.  It is owned by the
+// ChannelIDSource. If the operation can not complete synchronously, it will
+// notify the ChannelIDSource upon completion.
+class ChannelIDSourceChromium::Job {
+ public:
+  Job(ChannelIDSourceChromium* channel_id_source,
+      ChannelIDService* channel_id_service);
+
+  // Starts the channel ID lookup.  If |QUIC_PENDING| is returned, then
+  // |callback| will be invoked asynchronously when the operation completes.
+  QuicAsyncStatus GetChannelIDKey(const std::string& hostname,
+                                  scoped_ptr<ChannelIDKey>* channel_id_key,
+                                  ChannelIDSourceCallback* callback);
+
+ private:
+  enum State {
+    STATE_NONE,
+    STATE_GET_CHANNEL_ID_KEY,
+    STATE_GET_CHANNEL_ID_KEY_COMPLETE,
+  };
+
+  int DoLoop(int last_io_result);
+  void OnIOComplete(int result);
+  int DoGetChannelIDKey(int result);
+  int DoGetChannelIDKeyComplete(int result);
+
+  // Channel ID source to notify when this jobs completes.
+  ChannelIDSourceChromium* const channel_id_source_;
+
+  ChannelIDService* const channel_id_service_;
+
+  std::string channel_id_private_key_;
+  std::string channel_id_cert_;
+  ChannelIDService::RequestHandle channel_id_request_handle_;
+
+  // |hostname| specifies the hostname for which we need a channel ID.
+  std::string hostname_;
+
+  scoped_ptr<ChannelIDSourceCallback> callback_;
+
+  scoped_ptr<ChannelIDKey> channel_id_key_;
+
+  State next_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+ChannelIDSourceChromium::Job::Job(
+    ChannelIDSourceChromium* channel_id_source,
+    ChannelIDService* channel_id_service)
+    : channel_id_source_(channel_id_source),
+      channel_id_service_(channel_id_service),
+      next_state_(STATE_NONE) {
+}
+
+QuicAsyncStatus ChannelIDSourceChromium::Job::GetChannelIDKey(
+    const std::string& hostname,
+    scoped_ptr<ChannelIDKey>* channel_id_key,
+    ChannelIDSourceCallback* callback) {
+  DCHECK(channel_id_key);
+  DCHECK(callback);
+
+  if (STATE_NONE != next_state_) {
+    DLOG(DFATAL) << "GetChannelIDKey has begun";
+    return QUIC_FAILURE;
+  }
+
+  channel_id_key_.reset();
+
+  hostname_ = hostname;
+
+  next_state_ = STATE_GET_CHANNEL_ID_KEY;
+  switch (DoLoop(OK)) {
+    case OK:
+      channel_id_key->reset(channel_id_key_.release());
+      return QUIC_SUCCESS;
+    case ERR_IO_PENDING:
+      callback_.reset(callback);
+      return QUIC_PENDING;
+    default:
+      channel_id_key->reset();
+      return QUIC_FAILURE;
+  }
+}
+
+int ChannelIDSourceChromium::Job::DoLoop(int last_result) {
+  int rv = last_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_GET_CHANNEL_ID_KEY:
+        DCHECK(rv == OK);
+        rv = DoGetChannelIDKey(rv);
+        break;
+      case STATE_GET_CHANNEL_ID_KEY_COMPLETE:
+        rv = DoGetChannelIDKeyComplete(rv);
+        break;
+      case STATE_NONE:
+      default:
+        rv = ERR_UNEXPECTED;
+        LOG(DFATAL) << "unexpected state " << state;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+  return rv;
+}
+
+void ChannelIDSourceChromium::Job::OnIOComplete(int result) {
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    scoped_ptr<ChannelIDSourceCallback> callback(callback_.release());
+    callback->Run(&channel_id_key_);
+    // Will delete |this|.
+    channel_id_source_->OnJobComplete(this);
+  }
+}
+
+int ChannelIDSourceChromium::Job::DoGetChannelIDKey(int result) {
+  next_state_ = STATE_GET_CHANNEL_ID_KEY_COMPLETE;
+
+  return channel_id_service_->GetOrCreateChannelID(
+      hostname_,
+      &channel_id_private_key_,
+      &channel_id_cert_,
+      base::Bind(&ChannelIDSourceChromium::Job::OnIOComplete,
+                 base::Unretained(this)),
+      &channel_id_request_handle_);
+}
+
+int ChannelIDSourceChromium::Job::DoGetChannelIDKeyComplete(int result) {
+  DCHECK_EQ(STATE_NONE, next_state_);
+  if (result != OK) {
+    DLOG(WARNING) << "Failed to look up channel ID: " << ErrorToString(result);
+    return result;
+  }
+
+  std::vector<uint8> encrypted_private_key_info(
+      channel_id_private_key_.size());
+  memcpy(&encrypted_private_key_info[0], channel_id_private_key_.data(),
+         channel_id_private_key_.size());
+
+  base::StringPiece spki_piece;
+  if (!asn1::ExtractSPKIFromDERCert(channel_id_cert_, &spki_piece)) {
+    return ERR_UNEXPECTED;
+  }
+  std::vector<uint8> subject_public_key_info(spki_piece.size());
+  memcpy(&subject_public_key_info[0], spki_piece.data(), spki_piece.size());
+
+  crypto::ECPrivateKey* ec_private_key =
+      crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+          ChannelIDService::kEPKIPassword, encrypted_private_key_info,
+          subject_public_key_info);
+  if (!ec_private_key) {
+    // TODO(wtc): use the new error code ERR_CHANNEL_ID_IMPORT_FAILED to be
+    // added in https://codereview.chromium.org/338093012/.
+    return ERR_UNEXPECTED;
+  }
+  channel_id_key_.reset(new ChannelIDKeyChromium(ec_private_key));
+
+  return result;
+}
+
+ChannelIDSourceChromium::ChannelIDSourceChromium(
+    ChannelIDService* channel_id_service)
+    : channel_id_service_(channel_id_service) {
+}
+
+ChannelIDSourceChromium::~ChannelIDSourceChromium() {
+  STLDeleteElements(&active_jobs_);
+}
+
+QuicAsyncStatus ChannelIDSourceChromium::GetChannelIDKey(
+    const std::string& hostname,
+    scoped_ptr<ChannelIDKey>* channel_id_key,
+    ChannelIDSourceCallback* callback) {
+  scoped_ptr<Job> job(new Job(this, channel_id_service_));
+  QuicAsyncStatus status = job->GetChannelIDKey(hostname, channel_id_key,
+                                                callback);
+  if (status == QUIC_PENDING) {
+    active_jobs_.insert(job.release());
+  }
+  return status;
+}
+
+void ChannelIDSourceChromium::OnJobComplete(Job* job) {
+  active_jobs_.erase(job);
+  delete job;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/channel_id_chromium.h b/net/quic/crypto/channel_id_chromium.h
new file mode 100644
index 0000000..08cfff9
--- /dev/null
+++ b/net/quic/crypto/channel_id_chromium.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CHANNEL_ID_CHROMIUM_H_
+#define NET_QUIC_CRYPTO_CHANNEL_ID_CHROMIUM_H_
+
+#include <set>
+
+#include "net/quic/crypto/channel_id.h"
+
+namespace crypto {
+class ECPrivateKey;
+}  // namespace crypto
+
+namespace net {
+
+class ChannelIDService;
+
+class NET_EXPORT_PRIVATE ChannelIDKeyChromium: public ChannelIDKey {
+ public:
+  explicit ChannelIDKeyChromium(crypto::ECPrivateKey* ec_private_key);
+  virtual ~ChannelIDKeyChromium();
+
+  // ChannelIDKey interface
+  virtual bool Sign(base::StringPiece signed_data,
+                    std::string* out_signature) const OVERRIDE;
+  virtual std::string SerializeKey() const OVERRIDE;
+
+ private:
+  scoped_ptr<crypto::ECPrivateKey> ec_private_key_;
+};
+
+// ChannelIDSourceChromium implements the QUIC ChannelIDSource interface.
+class ChannelIDSourceChromium : public ChannelIDSource {
+ public:
+  explicit ChannelIDSourceChromium(
+      ChannelIDService* channel_id_service);
+  virtual ~ChannelIDSourceChromium();
+
+  // ChannelIDSource interface
+  virtual QuicAsyncStatus GetChannelIDKey(
+      const std::string& hostname,
+      scoped_ptr<ChannelIDKey>* channel_id_key,
+      ChannelIDSourceCallback* callback) OVERRIDE;
+
+ private:
+  class Job;
+  typedef std::set<Job*> JobSet;
+
+  void OnJobComplete(Job* job);
+
+  // Set owning pointers to active jobs.
+  JobSet active_jobs_;
+
+  // The service for retrieving Channel ID keys.
+  ChannelIDService* const channel_id_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChannelIDSourceChromium);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CHANNEL_ID_CHROMIUM_H_
diff --git a/net/quic/crypto/channel_id_nss.cc b/net/quic/crypto/channel_id_nss.cc
new file mode 100644
index 0000000..748e4ef
--- /dev/null
+++ b/net/quic/crypto/channel_id_nss.cc
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/channel_id.h"
+
+#include <keythi.h>
+#include <pk11pub.h>
+#include <sechash.h>
+
+using base::StringPiece;
+
+namespace net {
+
+// static
+bool ChannelIDVerifier::Verify(StringPiece key,
+                               StringPiece signed_data,
+                               StringPiece signature) {
+  return VerifyRaw(key, signed_data, signature, true);
+}
+
+// static
+bool ChannelIDVerifier::VerifyRaw(StringPiece key,
+                                  StringPiece signed_data,
+                                  StringPiece signature,
+                                  bool is_channel_id_signature) {
+  if (key.size() != 32 * 2 ||
+      signature.size() != 32 * 2) {
+    return false;
+  }
+
+  SECKEYPublicKey public_key;
+  memset(&public_key, 0, sizeof(public_key));
+
+  // DER encoding of the object identifier (OID) of the named curve P-256
+  // (1.2.840.10045.3.1.7). See RFC 6637 Section 11.
+  static const unsigned char p256_oid[] = {
+    0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+  };
+  public_key.keyType = ecKey;
+  public_key.u.ec.DEREncodedParams.type = siBuffer;
+  public_key.u.ec.DEREncodedParams.data = const_cast<unsigned char*>(p256_oid);
+  public_key.u.ec.DEREncodedParams.len = sizeof(p256_oid);
+
+  unsigned char key_buf[65];
+  key_buf[0] = 0x04;
+  memcpy(&key_buf[1], key.data(), key.size());
+  public_key.u.ec.publicValue.type = siBuffer;
+  public_key.u.ec.publicValue.data = key_buf;
+  public_key.u.ec.publicValue.len = sizeof(key_buf);
+
+  SECItem signature_item = {
+    siBuffer,
+    reinterpret_cast<unsigned char*>(const_cast<char*>(signature.data())),
+    static_cast<unsigned int>(signature.size())
+  };
+
+  unsigned char hash_buf[SHA256_LENGTH];
+  SECItem hash_item = { siBuffer, hash_buf, sizeof(hash_buf) };
+
+  HASHContext* sha256 = HASH_Create(HASH_AlgSHA256);
+  if (!sha256) {
+    return false;
+  }
+  HASH_Begin(sha256);
+  if (is_channel_id_signature) {
+    HASH_Update(sha256, reinterpret_cast<const unsigned char*>(kContextStr),
+                strlen(kContextStr) + 1);
+    HASH_Update(sha256,
+                reinterpret_cast<const unsigned char*>(kClientToServerStr),
+                strlen(kClientToServerStr) + 1);
+  }
+  HASH_Update(sha256,
+              reinterpret_cast<const unsigned char*>(signed_data.data()),
+              signed_data.size());
+  HASH_End(sha256, hash_buf, &hash_item.len, sizeof(hash_buf));
+  HASH_Destroy(sha256);
+
+  return PK11_Verify(&public_key, &signature_item, &hash_item, nullptr) ==
+         SECSuccess;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/channel_id_openssl.cc b/net/quic/crypto/channel_id_openssl.cc
new file mode 100644
index 0000000..f0cdcbf
--- /dev/null
+++ b/net/quic/crypto/channel_id_openssl.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/channel_id.h"
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/obj_mac.h>
+#include <openssl/sha.h>
+
+#include "crypto/openssl_util.h"
+#include "crypto/scoped_openssl_types.h"
+
+using base::StringPiece;
+
+namespace net {
+
+// static
+bool ChannelIDVerifier::Verify(StringPiece key,
+                               StringPiece signed_data,
+                               StringPiece signature) {
+  return VerifyRaw(key, signed_data, signature, true);
+}
+
+// static
+bool ChannelIDVerifier::VerifyRaw(StringPiece key,
+                                  StringPiece signed_data,
+                                  StringPiece signature,
+                                  bool is_channel_id_signature) {
+  if (key.size() != 32 * 2 ||
+      signature.size() != 32 * 2) {
+    return false;
+  }
+
+  crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free>::Type p256(
+      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+  if (p256.get() == nullptr) {
+    return false;
+  }
+
+  crypto::ScopedBIGNUM x(BN_new()), y(BN_new()), r(BN_new()), s(BN_new());
+
+  ECDSA_SIG sig;
+  sig.r = r.get();
+  sig.s = s.get();
+
+  const uint8* key_bytes = reinterpret_cast<const uint8*>(key.data());
+  const uint8* signature_bytes =
+      reinterpret_cast<const uint8*>(signature.data());
+
+  if (BN_bin2bn(key_bytes       +  0, 32, x.get()) == nullptr ||
+      BN_bin2bn(key_bytes       + 32, 32, y.get()) == nullptr ||
+      BN_bin2bn(signature_bytes +  0, 32, sig.r) == nullptr ||
+      BN_bin2bn(signature_bytes + 32, 32, sig.s) == nullptr) {
+    return false;
+  }
+
+  crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free>::Type point(
+      EC_POINT_new(p256.get()));
+  if (point.get() == nullptr ||
+      !EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x.get(),
+                                           y.get(), nullptr)) {
+    return false;
+  }
+
+  crypto::ScopedEC_KEY ecdsa_key(EC_KEY_new());
+  if (ecdsa_key.get() == nullptr ||
+      !EC_KEY_set_group(ecdsa_key.get(), p256.get()) ||
+      !EC_KEY_set_public_key(ecdsa_key.get(), point.get())) {
+    return false;
+  }
+
+  SHA256_CTX sha256;
+  SHA256_Init(&sha256);
+  if (is_channel_id_signature) {
+    SHA256_Update(&sha256, kContextStr, strlen(kContextStr) + 1);
+    SHA256_Update(&sha256, kClientToServerStr, strlen(kClientToServerStr) + 1);
+  }
+  SHA256_Update(&sha256, signed_data.data(), signed_data.size());
+
+  unsigned char digest[SHA256_DIGEST_LENGTH];
+  SHA256_Final(digest, &sha256);
+
+  return ECDSA_do_verify(digest, sizeof(digest), &sig, ecdsa_key.get()) == 1;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/channel_id_test.cc b/net/quic/crypto/channel_id_test.cc
new file mode 100644
index 0000000..7392723
--- /dev/null
+++ b/net/quic/crypto/channel_id_test.cc
@@ -0,0 +1,259 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/channel_id.h"
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+namespace {
+
+// The following ECDSA signature verification test vectors for P-256,SHA-256
+// come from the SigVer.rsp file in
+// http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip
+// downloaded on 2013-06-11.
+struct TestVector {
+  // Input:
+  const char* msg;
+  const char* qx;
+  const char* qy;
+  const char* r;
+  const char* s;
+
+  // Expected output:
+  bool result;  // true means "P", false means "F"
+};
+
+const TestVector test_vector[] = {
+  { "e4796db5f785f207aa30d311693b3702821dff1168fd2e04c0836825aefd850d"
+    "9aa60326d88cde1a23c7745351392ca2288d632c264f197d05cd424a30336c19"
+    "fd09bb229654f0222fcb881a4b35c290a093ac159ce13409111ff0358411133c"
+    "24f5b8e2090d6db6558afc36f06ca1f6ef779785adba68db27a409859fc4c4a0",
+    "87f8f2b218f49845f6f10eec3877136269f5c1a54736dbdf69f89940cad41555",
+    "e15f369036f49842fac7a86c8a2b0557609776814448b8f5e84aa9f4395205e9",
+    "d19ff48b324915576416097d2544f7cbdf8768b1454ad20e0baac50e211f23b0",
+    "a3e81e59311cdfff2d4784949f7a2cb50ba6c3a91fa54710568e61aca3e847c6",
+    false  // F (3 - S changed)
+  },
+  { "069a6e6b93dfee6df6ef6997cd80dd2182c36653cef10c655d524585655462d6"
+    "83877f95ecc6d6c81623d8fac4e900ed0019964094e7de91f1481989ae187300"
+    "4565789cbf5dc56c62aedc63f62f3b894c9c6f7788c8ecaadc9bd0e81ad91b2b"
+    "3569ea12260e93924fdddd3972af5273198f5efda0746219475017557616170e",
+    "5cf02a00d205bdfee2016f7421807fc38ae69e6b7ccd064ee689fc1a94a9f7d2",
+    "ec530ce3cc5c9d1af463f264d685afe2b4db4b5828d7e61b748930f3ce622a85",
+    "dc23d130c6117fb5751201455e99f36f59aba1a6a21cf2d0e7481a97451d6693",
+    "d6ce7708c18dbf35d4f8aa7240922dc6823f2e7058cbc1484fcad1599db5018c",
+    false  // F (2 - R changed)
+  },
+  { "df04a346cf4d0e331a6db78cca2d456d31b0a000aa51441defdb97bbeb20b94d"
+    "8d746429a393ba88840d661615e07def615a342abedfa4ce912e562af7149598"
+    "96858af817317a840dcff85a057bb91a3c2bf90105500362754a6dd321cdd861"
+    "28cfc5f04667b57aa78c112411e42da304f1012d48cd6a7052d7de44ebcc01de",
+    "2ddfd145767883ffbb0ac003ab4a44346d08fa2570b3120dcce94562422244cb",
+    "5f70c7d11ac2b7a435ccfbbae02c3df1ea6b532cc0e9db74f93fffca7c6f9a64",
+    "9913111cff6f20c5bf453a99cd2c2019a4e749a49724a08774d14e4c113edda8",
+    "9467cd4cd21ecb56b0cab0a9a453b43386845459127a952421f5c6382866c5cc",
+    false  // F (4 - Q changed)
+  },
+  { "e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45f"
+    "d75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217"
+    "ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce4"
+    "70a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3",
+    "e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c",
+    "970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927",
+    "bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f",
+    "17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c",
+    true  // P (0 )
+  },
+  { "73c5f6a67456ae48209b5f85d1e7de7758bf235300c6ae2bdceb1dcb27a7730f"
+    "b68c950b7fcada0ecc4661d3578230f225a875e69aaa17f1e71c6be5c831f226"
+    "63bac63d0c7a9635edb0043ff8c6f26470f02a7bc56556f1437f06dfa27b487a"
+    "6c4290d8bad38d4879b334e341ba092dde4e4ae694a9c09302e2dbf443581c08",
+    "e0fc6a6f50e1c57475673ee54e3a57f9a49f3328e743bf52f335e3eeaa3d2864",
+    "7f59d689c91e463607d9194d99faf316e25432870816dde63f5d4b373f12f22a",
+    "1d75830cd36f4c9aa181b2c4221e87f176b7f05b7c87824e82e396c88315c407",
+    "cb2acb01dac96efc53a32d4a0d85d0c2e48955214783ecf50a4f0414a319c05a",
+    true  // P (0 )
+  },
+  { "666036d9b4a2426ed6585a4e0fd931a8761451d29ab04bd7dc6d0c5b9e38e6c2"
+    "b263ff6cb837bd04399de3d757c6c7005f6d7a987063cf6d7e8cb38a4bf0d74a"
+    "282572bd01d0f41e3fd066e3021575f0fa04f27b700d5b7ddddf50965993c3f9"
+    "c7118ed78888da7cb221849b3260592b8e632d7c51e935a0ceae15207bedd548",
+    "a849bef575cac3c6920fbce675c3b787136209f855de19ffe2e8d29b31a5ad86",
+    "bf5fe4f7858f9b805bd8dcc05ad5e7fb889de2f822f3d8b41694e6c55c16b471",
+    "25acc3aa9d9e84c7abf08f73fa4195acc506491d6fc37cb9074528a7db87b9d6",
+    "9b21d5b5259ed3f2ef07dfec6cc90d3a37855d1ce122a85ba6a333f307d31537",
+    false  // F (2 - R changed)
+  },
+  { "7e80436bce57339ce8da1b5660149a20240b146d108deef3ec5da4ae256f8f89"
+    "4edcbbc57b34ce37089c0daa17f0c46cd82b5a1599314fd79d2fd2f446bd5a25"
+    "b8e32fcf05b76d644573a6df4ad1dfea707b479d97237a346f1ec632ea5660ef"
+    "b57e8717a8628d7f82af50a4e84b11f21bdff6839196a880ae20b2a0918d58cd",
+    "3dfb6f40f2471b29b77fdccba72d37c21bba019efa40c1c8f91ec405d7dcc5df",
+    "f22f953f1e395a52ead7f3ae3fc47451b438117b1e04d613bc8555b7d6e6d1bb",
+    "548886278e5ec26bed811dbb72db1e154b6f17be70deb1b210107decb1ec2a5a",
+    "e93bfebd2f14f3d827ca32b464be6e69187f5edbd52def4f96599c37d58eee75",
+    false  // F (4 - Q changed)
+  },
+  { "1669bfb657fdc62c3ddd63269787fc1c969f1850fb04c933dda063ef74a56ce1"
+    "3e3a649700820f0061efabf849a85d474326c8a541d99830eea8131eaea584f2"
+    "2d88c353965dabcdc4bf6b55949fd529507dfb803ab6b480cd73ca0ba00ca19c"
+    "438849e2cea262a1c57d8f81cd257fb58e19dec7904da97d8386e87b84948169",
+    "69b7667056e1e11d6caf6e45643f8b21e7a4bebda463c7fdbc13bc98efbd0214",
+    "d3f9b12eb46c7c6fda0da3fc85bc1fd831557f9abc902a3be3cb3e8be7d1aa2f",
+    "288f7a1cd391842cce21f00e6f15471c04dc182fe4b14d92dc18910879799790",
+    "247b3c4e89a3bcadfea73c7bfd361def43715fa382b8c3edf4ae15d6e55e9979",
+    false  // F (1 - Message changed)
+  },
+  { "3fe60dd9ad6caccf5a6f583b3ae65953563446c4510b70da115ffaa0ba04c076"
+    "115c7043ab8733403cd69c7d14c212c655c07b43a7c71b9a4cffe22c2684788e"
+    "c6870dc2013f269172c822256f9e7cc674791bf2d8486c0f5684283e1649576e"
+    "fc982ede17c7b74b214754d70402fb4bb45ad086cf2cf76b3d63f7fce39ac970",
+    "bf02cbcf6d8cc26e91766d8af0b164fc5968535e84c158eb3bc4e2d79c3cc682",
+    "069ba6cb06b49d60812066afa16ecf7b51352f2c03bd93ec220822b1f3dfba03",
+    "f5acb06c59c2b4927fb852faa07faf4b1852bbb5d06840935e849c4d293d1bad",
+    "049dab79c89cc02f1484c437f523e080a75f134917fda752f2d5ca397addfe5d",
+    false  // F (3 - S changed)
+  },
+  { "983a71b9994d95e876d84d28946a041f8f0a3f544cfcc055496580f1dfd4e312"
+    "a2ad418fe69dbc61db230cc0c0ed97e360abab7d6ff4b81ee970a7e97466acfd"
+    "9644f828ffec538abc383d0e92326d1c88c55e1f46a668a039beaa1be631a891"
+    "29938c00a81a3ae46d4aecbf9707f764dbaccea3ef7665e4c4307fa0b0a3075c",
+    "224a4d65b958f6d6afb2904863efd2a734b31798884801fcab5a590f4d6da9de",
+    "178d51fddada62806f097aa615d33b8f2404e6b1479f5fd4859d595734d6d2b9",
+    "87b93ee2fecfda54deb8dff8e426f3c72c8864991f8ec2b3205bb3b416de93d2",
+    "4044a24df85be0cc76f21a4430b75b8e77b932a87f51e4eccbc45c263ebf8f66",
+    false  // F (2 - R changed)
+  },
+  { "4a8c071ac4fd0d52faa407b0fe5dab759f7394a5832127f2a3498f34aac28733"
+    "9e043b4ffa79528faf199dc917f7b066ad65505dab0e11e6948515052ce20cfd"
+    "b892ffb8aa9bf3f1aa5be30a5bbe85823bddf70b39fd7ebd4a93a2f75472c1d4"
+    "f606247a9821f1a8c45a6cb80545de2e0c6c0174e2392088c754e9c8443eb5af",
+    "43691c7795a57ead8c5c68536fe934538d46f12889680a9cb6d055a066228369",
+    "f8790110b3c3b281aa1eae037d4f1234aff587d903d93ba3af225c27ddc9ccac",
+    "8acd62e8c262fa50dd9840480969f4ef70f218ebf8ef9584f199031132c6b1ce",
+    "cfca7ed3d4347fb2a29e526b43c348ae1ce6c60d44f3191b6d8ea3a2d9c92154",
+    false  // F (3 - S changed)
+  },
+  { "0a3a12c3084c865daf1d302c78215d39bfe0b8bf28272b3c0b74beb4b7409db0"
+    "718239de700785581514321c6440a4bbaea4c76fa47401e151e68cb6c29017f0"
+    "bce4631290af5ea5e2bf3ed742ae110b04ade83a5dbd7358f29a85938e23d87a"
+    "c8233072b79c94670ff0959f9c7f4517862ff829452096c78f5f2e9a7e4e9216",
+    "9157dbfcf8cf385f5bb1568ad5c6e2a8652ba6dfc63bc1753edf5268cb7eb596",
+    "972570f4313d47fc96f7c02d5594d77d46f91e949808825b3d31f029e8296405",
+    "dfaea6f297fa320b707866125c2a7d5d515b51a503bee817de9faa343cc48eeb",
+    "8f780ad713f9c3e5a4f7fa4c519833dfefc6a7432389b1e4af463961f09764f2",
+    false  // F (1 - Message changed)
+  },
+  { "785d07a3c54f63dca11f5d1a5f496ee2c2f9288e55007e666c78b007d95cc285"
+    "81dce51f490b30fa73dc9e2d45d075d7e3a95fb8a9e1465ad191904124160b7c"
+    "60fa720ef4ef1c5d2998f40570ae2a870ef3e894c2bc617d8a1dc85c3c557749"
+    "28c38789b4e661349d3f84d2441a3b856a76949b9f1f80bc161648a1cad5588e",
+    "072b10c081a4c1713a294f248aef850e297991aca47fa96a7470abe3b8acfdda",
+    "9581145cca04a0fb94cedce752c8f0370861916d2a94e7c647c5373ce6a4c8f5",
+    "09f5483eccec80f9d104815a1be9cc1a8e5b12b6eb482a65c6907b7480cf4f19",
+    "a4f90e560c5e4eb8696cb276e5165b6a9d486345dedfb094a76e8442d026378d",
+    false  // F (4 - Q changed)
+  },
+  { "76f987ec5448dd72219bd30bf6b66b0775c80b394851a43ff1f537f140a6e722"
+    "9ef8cd72ad58b1d2d20298539d6347dd5598812bc65323aceaf05228f738b5ad"
+    "3e8d9fe4100fd767c2f098c77cb99c2992843ba3eed91d32444f3b6db6cd212d"
+    "d4e5609548f4bb62812a920f6e2bf1581be1ebeebdd06ec4e971862cc42055ca",
+    "09308ea5bfad6e5adf408634b3d5ce9240d35442f7fe116452aaec0d25be8c24",
+    "f40c93e023ef494b1c3079b2d10ef67f3170740495ce2cc57f8ee4b0618b8ee5",
+    "5cc8aa7c35743ec0c23dde88dabd5e4fcd0192d2116f6926fef788cddb754e73",
+    "9c9c045ebaa1b828c32f82ace0d18daebf5e156eb7cbfdc1eff4399a8a900ae7",
+    false  // F (1 - Message changed)
+  },
+  { "60cd64b2cd2be6c33859b94875120361a24085f3765cb8b2bf11e026fa9d8855"
+    "dbe435acf7882e84f3c7857f96e2baab4d9afe4588e4a82e17a78827bfdb5ddb"
+    "d1c211fbc2e6d884cddd7cb9d90d5bf4a7311b83f352508033812c776a0e00c0"
+    "03c7e0d628e50736c7512df0acfa9f2320bd102229f46495ae6d0857cc452a84",
+    "2d98ea01f754d34bbc3003df5050200abf445ec728556d7ed7d5c54c55552b6d",
+    "9b52672742d637a32add056dfd6d8792f2a33c2e69dafabea09b960bc61e230a",
+    "06108e525f845d0155bf60193222b3219c98e3d49424c2fb2a0987f825c17959",
+    "62b5cdd591e5b507e560167ba8f6f7cda74673eb315680cb89ccbc4eec477dce",
+    true  // P (0 )
+  },
+  { NULL }
+};
+
+}  // namespace
+
+// A known answer test for ChannelIDVerifier.
+TEST(ChannelIDTest, VerifyKnownAnswerTest) {
+  string msg;
+  string qx;
+  string qy;
+  string r;
+  string s;
+
+  for (size_t i = 0; test_vector[i].msg != nullptr; i++) {
+    SCOPED_TRACE(i);
+    // Decode the test vector.
+    ASSERT_TRUE(DecodeHexString(test_vector[i].msg, &msg));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].qx, &qx));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].qy, &qy));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].r, &r));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].s, &s));
+
+    string key = qx + qy;
+    string signature = r + s;
+
+    // The test vector's lengths should look sane.
+    EXPECT_EQ(32u, qx.size());
+    EXPECT_EQ(32u, qy.size());
+    EXPECT_EQ(32u, r.size());
+    EXPECT_EQ(32u, s.size());
+
+    EXPECT_EQ(test_vector[i].result,
+              ChannelIDVerifier::VerifyRaw(key, msg, signature, false));
+  }
+}
+
+TEST(ChannelIDTest, SignAndVerify) {
+  scoped_ptr<ChannelIDSource> source(
+      CryptoTestUtils::ChannelIDSourceForTesting());
+
+  const string signed_data = "signed data";
+  const string hostname = "foo.example.com";
+  scoped_ptr<ChannelIDKey> channel_id_key;
+  QuicAsyncStatus status =
+      source->GetChannelIDKey(hostname, &channel_id_key, nullptr);
+  ASSERT_EQ(QUIC_SUCCESS, status);
+
+  string signature;
+  ASSERT_TRUE(channel_id_key->Sign(signed_data, &signature));
+
+  string key = channel_id_key->SerializeKey();
+  EXPECT_TRUE(ChannelIDVerifier::Verify(key, signed_data, signature));
+
+  EXPECT_FALSE(ChannelIDVerifier::Verify("a" + key, signed_data, signature));
+  EXPECT_FALSE(ChannelIDVerifier::Verify(key, "a" + signed_data, signature));
+
+  scoped_ptr<char[]> bad_key(new char[key.size()]);
+  memcpy(bad_key.get(), key.data(), key.size());
+  bad_key[1] ^= 0x80;
+  EXPECT_FALSE(ChannelIDVerifier::Verify(
+      string(bad_key.get(), key.size()), signed_data, signature));
+
+  scoped_ptr<char[]> bad_signature(new char[signature.size()]);
+  memcpy(bad_signature.get(), signature.data(), signature.size());
+  bad_signature[1] ^= 0x80;
+  EXPECT_FALSE(ChannelIDVerifier::Verify(
+      key, signed_data, string(bad_signature.get(), signature.size())));
+
+  EXPECT_FALSE(ChannelIDVerifier::Verify(
+      key, "wrong signed data", signature));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/common_cert_set.cc b/net/quic/crypto/common_cert_set.cc
new file mode 100644
index 0000000..f631cd6
--- /dev/null
+++ b/net/quic/crypto/common_cert_set.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/common_cert_set.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace common_cert_set_0 {
+#include "net/quic/crypto/common_cert_set_0.c"
+}
+
+namespace {
+
+struct CertSet {
+  // num_certs contains the number of certificates in this set.
+  size_t num_certs;
+  // certs is an array of |num_certs| pointers to the DER encoded certificates.
+  const unsigned char* const* certs;
+  // lens is an array of |num_certs| integers describing the length, in bytes,
+  // of each certificate.
+  const size_t* lens;
+  // hash contains the 64-bit, FNV-1a hash of this set.
+  uint64 hash;
+};
+
+const CertSet kSets[] = {
+  {
+    common_cert_set_0::kNumCerts,
+    common_cert_set_0::kCerts,
+    common_cert_set_0::kLens,
+    common_cert_set_0::kHash,
+  },
+};
+
+const uint64 kSetHashes[] = {
+  common_cert_set_0::kHash,
+};
+
+// Compare returns a value less than, equal to or greater than zero if |a| is
+// lexicographically less than, equal to or greater than |b|, respectively.
+int Compare(StringPiece a, const unsigned char* b, size_t b_len) {
+  size_t len = a.size();
+  if (len > b_len) {
+    len = b_len;
+  }
+  int n = memcmp(a.data(), b, len);
+  if (n != 0) {
+    return n;
+  }
+
+  if (a.size() < b_len) {
+    return -1;
+  } else if (a.size() > b_len) {
+    return 1;
+  }
+  return 0;
+}
+
+// CommonCertSetsQUIC implements the CommonCertSets interface using the default
+// certificate sets.
+class CommonCertSetsQUIC : public CommonCertSets {
+ public:
+  // CommonCertSets interface.
+  virtual StringPiece GetCommonHashes() const OVERRIDE {
+    return StringPiece(reinterpret_cast<const char*>(kSetHashes),
+                       sizeof(uint64) * arraysize(kSetHashes));
+  }
+
+  virtual StringPiece GetCert(uint64 hash, uint32 index) const OVERRIDE {
+    for (size_t i = 0; i < arraysize(kSets); i++) {
+      if (kSets[i].hash == hash) {
+        if (index < kSets[i].num_certs) {
+          return StringPiece(
+              reinterpret_cast<const char*>(kSets[i].certs[index]),
+              kSets[i].lens[index]);
+        }
+        break;
+      }
+    }
+
+    return StringPiece();
+  }
+
+  virtual bool MatchCert(StringPiece cert, StringPiece common_set_hashes,
+                         uint64* out_hash, uint32* out_index) const OVERRIDE {
+    if (common_set_hashes.size() % sizeof(uint64) != 0) {
+      return false;
+    }
+
+    for (size_t i = 0; i < common_set_hashes.size() / sizeof(uint64); i++) {
+      uint64 hash;
+      memcpy(&hash, common_set_hashes.data() + i * sizeof(uint64),
+             sizeof(uint64));
+
+      for (size_t j = 0; j < arraysize(kSets); j++) {
+        if (kSets[j].hash != hash) {
+          continue;
+        }
+
+        if (kSets[j].num_certs == 0) {
+          continue;
+        }
+
+        // Binary search for a matching certificate.
+        size_t min = 0;
+        size_t max = kSets[j].num_certs - 1;
+        while (max >= min) {
+          size_t mid = min + ((max - min) / 2);
+          int n = Compare(cert, kSets[j].certs[mid], kSets[j].lens[mid]);
+          if (n < 0) {
+            if (mid == 0) {
+              break;
+            }
+            max = mid - 1;
+          } else if (n > 0) {
+            min = mid + 1;
+          } else {
+            *out_hash = hash;
+            *out_index = mid;
+            return true;
+          }
+        }
+      }
+    }
+
+    return false;
+  }
+
+  static CommonCertSetsQUIC* GetInstance() {
+    return Singleton<CommonCertSetsQUIC>::get();
+  }
+
+ private:
+  CommonCertSetsQUIC() {}
+  virtual ~CommonCertSetsQUIC() {}
+
+  friend struct DefaultSingletonTraits<CommonCertSetsQUIC>;
+  DISALLOW_COPY_AND_ASSIGN(CommonCertSetsQUIC);
+};
+
+}  // anonymous namespace
+
+CommonCertSets::~CommonCertSets() {}
+
+// static
+const CommonCertSets* CommonCertSets::GetInstanceQUIC() {
+  return CommonCertSetsQUIC::GetInstance();
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/common_cert_set.h b/net/quic/crypto/common_cert_set.h
new file mode 100644
index 0000000..a9e9304
--- /dev/null
+++ b/net/quic/crypto/common_cert_set.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_COMMON_CERT_SET_H_
+#define NET_QUIC_CRYPTO_COMMON_CERT_SET_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// CommonCertSets is an interface to an object that contains a number of common
+// certificate sets and can match against them.
+class NET_EXPORT_PRIVATE CommonCertSets {
+ public:
+  virtual ~CommonCertSets();
+
+  // GetInstanceQUIC returns the standard QUIC common certificate sets.
+  static const CommonCertSets* GetInstanceQUIC();
+
+  // GetCommonHashes returns a StringPiece containing the hashes of common sets
+  // supported by this object. The 64-bit hashes are concatenated in the
+  // StringPiece.
+  virtual base::StringPiece GetCommonHashes() const = 0;
+
+  // GetCert returns a specific certificate (at index |index|) in the common
+  // set identified by |hash|. If no such certificate is known, an empty
+  // StringPiece is returned.
+  virtual base::StringPiece GetCert(uint64 hash, uint32 index) const = 0;
+
+  // MatchCert tries to find |cert| in one of the common certificate sets
+  // identified by |common_set_hashes|. On success it puts the hash of the
+  // set in |out_hash|, the index of |cert| in the set in |out_index| and
+  // returns true. Otherwise it returns false.
+  virtual bool MatchCert(base::StringPiece cert,
+                         base::StringPiece common_set_hashes,
+                         uint64* out_hash,
+                         uint32* out_index) const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_COMMON_CERT_SET_H_
diff --git a/net/quic/crypto/common_cert_set_0.c b/net/quic/crypto/common_cert_set_0.c
new file mode 100644
index 0000000..1133733
--- /dev/null
+++ b/net/quic/crypto/common_cert_set_0.c
@@ -0,0 +1,223 @@
+/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#include "net/quic/crypto/common_cert_set_1_50.inc"
+#include "net/quic/crypto/common_cert_set_51_100.inc"
+
+static const size_t kNumCerts = 102;
+static const unsigned char* const kCerts[] = {
+  kDERCert0,
+  kDERCert1,
+  kDERCert2,
+  kDERCert3,
+  kDERCert4,
+  kDERCert5,
+  kDERCert6,
+  kDERCert7,
+  kDERCert8,
+  kDERCert9,
+  kDERCert10,
+  kDERCert11,
+  kDERCert12,
+  kDERCert13,
+  kDERCert14,
+  kDERCert15,
+  kDERCert16,
+  kDERCert17,
+  kDERCert18,
+  kDERCert19,
+  kDERCert20,
+  kDERCert21,
+  kDERCert22,
+  kDERCert23,
+  kDERCert24,
+  kDERCert25,
+  kDERCert26,
+  kDERCert27,
+  kDERCert28,
+  kDERCert29,
+  kDERCert30,
+  kDERCert31,
+  kDERCert32,
+  kDERCert33,
+  kDERCert34,
+  kDERCert35,
+  kDERCert36,
+  kDERCert37,
+  kDERCert38,
+  kDERCert39,
+  kDERCert40,
+  kDERCert41,
+  kDERCert42,
+  kDERCert43,
+  kDERCert44,
+  kDERCert45,
+  kDERCert46,
+  kDERCert47,
+  kDERCert48,
+  kDERCert49,
+  kDERCert50,
+  kDERCert51,
+  kDERCert52,
+  kDERCert53,
+  kDERCert54,
+  kDERCert55,
+  kDERCert56,
+  kDERCert57,
+  kDERCert58,
+  kDERCert59,
+  kDERCert60,
+  kDERCert61,
+  kDERCert62,
+  kDERCert63,
+  kDERCert64,
+  kDERCert65,
+  kDERCert66,
+  kDERCert67,
+  kDERCert68,
+  kDERCert69,
+  kDERCert70,
+  kDERCert71,
+  kDERCert72,
+  kDERCert73,
+  kDERCert74,
+  kDERCert75,
+  kDERCert76,
+  kDERCert77,
+  kDERCert78,
+  kDERCert79,
+  kDERCert80,
+  kDERCert81,
+  kDERCert82,
+  kDERCert83,
+  kDERCert84,
+  kDERCert85,
+  kDERCert86,
+  kDERCert87,
+  kDERCert88,
+  kDERCert89,
+  kDERCert90,
+  kDERCert91,
+  kDERCert92,
+  kDERCert93,
+  kDERCert94,
+  kDERCert95,
+  kDERCert96,
+  kDERCert97,
+  kDERCert98,
+  kDERCert99,
+  kDERCert100,
+  kDERCert101,
+};
+
+static const size_t kLens[] = {
+  692,
+  897,
+  903,
+  911,
+  911,
+  957,
+  971,
+  985,
+  989,
+  1022,
+  1032,
+  1049,
+  1055,
+  1071,
+  1071,
+  1073,
+  1075,
+  1078,
+  1080,
+  1082,
+  1082,
+  1084,
+  1088,
+  1090,
+  1094,
+  1097,
+  1098,
+  1107,
+  1118,
+  1119,
+  1122,
+  1124,
+  1131,
+  1134,
+  1136,
+  1147,
+  1161,
+  1162,
+  1162,
+  1167,
+  1167,
+  1171,
+  1172,
+  1181,
+  1183,
+  1184,
+  1187,
+  1191,
+  1194,
+  1194,
+  1199,
+  1223,
+  1226,
+  1231,
+  1236,
+  1239,
+  1250,
+  1254,
+  1256,
+  1256,
+  1257,
+  1260,
+  1268,
+  1269,
+  1269,
+  1270,
+  1273,
+  1276,
+  1279,
+  1280,
+  1282,
+  1285,
+  1286,
+  1287,
+  1287,
+  1290,
+  1291,
+  1291,
+  1294,
+  1294,
+  1297,
+  1302,
+  1302,
+  1303,
+  1311,
+  1330,
+  1365,
+  1512,
+  1520,
+  1535,
+  1548,
+  1550,
+  1559,
+  1570,
+  1581,
+  1584,
+  1592,
+  1592,
+  1625,
+  1628,
+  1767,
+  1770,
+};
+
+static const uint64 kHash = GG_UINT64_C(0xc9fef74053f99f39);
diff --git a/net/quic/crypto/common_cert_set_1_50.inc b/net/quic/crypto/common_cert_set_1_50.inc
new file mode 100644
index 0000000..5ee471b
--- /dev/null
+++ b/net/quic/crypto/common_cert_set_1_50.inc
@@ -0,0 +1,9794 @@
+/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 747377 (0xb6771)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+        Validity
+            Not Before: Jun  8 20:43:27 2009 GMT
+            Not After : Jun  7 19:43:27 2013 GMT
+        Subject: C=US, O=Google Inc, CN=Google Internet Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:c9:ed:b7:a4:8b:9c:57:e7:84:3e:40:7d:84:f4:
+                    8f:d1:71:63:53:99:e7:79:74:14:af:44:99:33:20:
+                    92:8d:7b:e5:28:0c:ba:ad:6c:49:7e:83:5f:34:59:
+                    4e:0a:7a:30:cd:d0:d7:c4:57:45:ed:d5:aa:d6:73:
+                    26:ce:ad:32:13:b8:d7:0f:1d:3b:df:dd:dc:08:36:
+                    a8:6f:51:44:9b:ca:d6:20:52:73:b7:26:87:35:6a:
+                    db:a9:e5:d4:59:a5:2b:fc:67:19:39:fa:93:18:18:
+                    6c:de:dd:25:8a:0e:33:14:47:c2:ef:01:50:79:e4:
+                    fd:69:d1:a7:c0:ac:e2:57:6f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                BF:C0:30:EB:F5:43:11:3E:67:BA:9E:91:FB:FC:6A:DA:E3:6B:12:24
+            X509v3 Authority Key Identifier: 
+                keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/secureca.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         b8:8a:23:c6:48:96:b1:11:7c:60:77:5e:05:9a:ab:a1:c6:fa:
+         82:1c:18:07:c4:eb:81:b0:a8:66:eb:49:a8:e9:0c:d3:29:ad:
+         f5:ef:24:4c:fd:e4:4b:ca:7f:5e:63:ab:99:27:cb:9f:36:21:
+         2c:b9:10:60:67:cd:d2:b4:f0:f0:ab:71:e5:8b:5a:89:27:11:
+         84:aa:8e:bf:99:f0:9d:09:21:0a:52:19:9a:5a:09:d2:90:b7:
+         fa:0c:f8:7e:78:a2:b0:85:af:5c:4c:99:d9:5c:55:29:f9:a5:
+         51:42:2e:3a:cb:38:8c:78:3b:cb:f8:fb:95:87:bc:bc:90:f9:
+         50:32
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3
+WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
+R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
+NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
+qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
+oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk
+MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
+Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde
+BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN
+0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml
+UUIuOss4jHg7y/j7lYe8vJD5UDI=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert0[] = {
+  0x30, 0x82, 0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x0b, 0x67, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+  0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30,
+  0x36, 0x30, 0x38, 0x32, 0x30, 0x34, 0x33, 0x32, 0x37, 0x5a, 0x17, 0x0d,
+  0x31, 0x33, 0x30, 0x36, 0x30, 0x37, 0x31, 0x39, 0x34, 0x33, 0x32, 0x37,
+  0x5a, 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+  0x63, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+  0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+  0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+  0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xc9, 0xed, 0xb7, 0xa4, 0x8b, 0x9c,
+  0x57, 0xe7, 0x84, 0x3e, 0x40, 0x7d, 0x84, 0xf4, 0x8f, 0xd1, 0x71, 0x63,
+  0x53, 0x99, 0xe7, 0x79, 0x74, 0x14, 0xaf, 0x44, 0x99, 0x33, 0x20, 0x92,
+  0x8d, 0x7b, 0xe5, 0x28, 0x0c, 0xba, 0xad, 0x6c, 0x49, 0x7e, 0x83, 0x5f,
+  0x34, 0x59, 0x4e, 0x0a, 0x7a, 0x30, 0xcd, 0xd0, 0xd7, 0xc4, 0x57, 0x45,
+  0xed, 0xd5, 0xaa, 0xd6, 0x73, 0x26, 0xce, 0xad, 0x32, 0x13, 0xb8, 0xd7,
+  0x0f, 0x1d, 0x3b, 0xdf, 0xdd, 0xdc, 0x08, 0x36, 0xa8, 0x6f, 0x51, 0x44,
+  0x9b, 0xca, 0xd6, 0x20, 0x52, 0x73, 0xb7, 0x26, 0x87, 0x35, 0x6a, 0xdb,
+  0xa9, 0xe5, 0xd4, 0x59, 0xa5, 0x2b, 0xfc, 0x67, 0x19, 0x39, 0xfa, 0x93,
+  0x18, 0x18, 0x6c, 0xde, 0xdd, 0x25, 0x8a, 0x0e, 0x33, 0x14, 0x47, 0xc2,
+  0xef, 0x01, 0x50, 0x79, 0xe4, 0xfd, 0x69, 0xd1, 0xa7, 0xc0, 0xac, 0xe2,
+  0x57, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa3, 0x30, 0x81,
+  0xa0, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0xbf, 0xc0, 0x30, 0xeb, 0xf5, 0x43, 0x11, 0x3e,
+  0x67, 0xba, 0x9e, 0x91, 0xfb, 0xfc, 0x6a, 0xda, 0xe3, 0x6b, 0x12, 0x24,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8,
+  0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x12, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+  0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67,
+  0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
+  0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+  0xb8, 0x8a, 0x23, 0xc6, 0x48, 0x96, 0xb1, 0x11, 0x7c, 0x60, 0x77, 0x5e,
+  0x05, 0x9a, 0xab, 0xa1, 0xc6, 0xfa, 0x82, 0x1c, 0x18, 0x07, 0xc4, 0xeb,
+  0x81, 0xb0, 0xa8, 0x66, 0xeb, 0x49, 0xa8, 0xe9, 0x0c, 0xd3, 0x29, 0xad,
+  0xf5, 0xef, 0x24, 0x4c, 0xfd, 0xe4, 0x4b, 0xca, 0x7f, 0x5e, 0x63, 0xab,
+  0x99, 0x27, 0xcb, 0x9f, 0x36, 0x21, 0x2c, 0xb9, 0x10, 0x60, 0x67, 0xcd,
+  0xd2, 0xb4, 0xf0, 0xf0, 0xab, 0x71, 0xe5, 0x8b, 0x5a, 0x89, 0x27, 0x11,
+  0x84, 0xaa, 0x8e, 0xbf, 0x99, 0xf0, 0x9d, 0x09, 0x21, 0x0a, 0x52, 0x19,
+  0x9a, 0x5a, 0x09, 0xd2, 0x90, 0xb7, 0xfa, 0x0c, 0xf8, 0x7e, 0x78, 0xa2,
+  0xb0, 0x85, 0xaf, 0x5c, 0x4c, 0x99, 0xd9, 0x5c, 0x55, 0x29, 0xf9, 0xa5,
+  0x51, 0x42, 0x2e, 0x3a, 0xcb, 0x38, 0x8c, 0x78, 0x3b, 0xcb, 0xf8, 0xfb,
+  0x95, 0x87, 0xbc, 0xbc, 0x90, 0xf9, 0x50, 0x32,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1227750 (0x12bbe6)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+        Validity
+            Not Before: May 21 04:00:00 2002 GMT
+            Not After : Aug 21 04:00:00 2018 GMT
+        Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df:
+                    3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8:
+                    43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29:
+                    bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4:
+                    60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3:
+                    ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92:
+                    2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d:
+                    80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14:
+                    15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd:
+                    d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6:
+                    d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5:
+                    5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39:
+                    19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05:
+                    9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2:
+                    fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32:
+                    eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07:
+                    36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b:
+                    e4:f9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+            X509v3 Subject Key Identifier: 
+                C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/secureca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.geotrust.com/resources/repository
+
+    Signature Algorithm: sha1WithRSAEncryption
+         76:e1:12:6e:4e:4b:16:12:86:30:06:b2:81:08:cf:f0:08:c7:
+         c7:71:7e:66:ee:c2:ed:d4:3b:1f:ff:f0:f0:c8:4e:d6:43:38:
+         b0:b9:30:7d:18:d0:55:83:a2:6a:cb:36:11:9c:e8:48:66:a3:
+         6d:7f:b8:13:d4:47:fe:8b:5a:5c:73:fc:ae:d9:1b:32:19:38:
+         ab:97:34:14:aa:96:d2:eb:a3:1c:14:08:49:b6:bb:e5:91:ef:
+         83:36:eb:1d:56:6f:ca:da:bc:73:63:90:e4:7f:7b:3e:22:cb:
+         3d:07:ed:5f:38:74:9c:e3:03:50:4e:a1:af:98:ee:61:f2:84:
+         3f:12
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert1[] = {
+  0x30, 0x82, 0x03, 0x7d, 0x30, 0x82, 0x02, 0xe6, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x12, 0xbb, 0xe6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+  0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x32, 0x30,
+  0x35, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d,
+  0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x12, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0xcc, 0x18, 0x63, 0x30, 0xfd,
+  0xf4, 0x17, 0x23, 0x1a, 0x56, 0x7e, 0x5b, 0xdf, 0x3c, 0x6c, 0x38, 0xe4,
+  0x71, 0xb7, 0x78, 0x91, 0xd4, 0xbc, 0xa1, 0xd8, 0x4c, 0xf8, 0xa8, 0x43,
+  0xb6, 0x03, 0xe9, 0x4d, 0x21, 0x07, 0x08, 0x88, 0xda, 0x58, 0x2f, 0x66,
+  0x39, 0x29, 0xbd, 0x05, 0x78, 0x8b, 0x9d, 0x38, 0xe8, 0x05, 0xb7, 0x6a,
+  0x7e, 0x71, 0xa4, 0xe6, 0xc4, 0x60, 0xa6, 0xb0, 0xef, 0x80, 0xe4, 0x89,
+  0x28, 0x0f, 0x9e, 0x25, 0xd6, 0xed, 0x83, 0xf3, 0xad, 0xa6, 0x91, 0xc7,
+  0x98, 0xc9, 0x42, 0x18, 0x35, 0x14, 0x9d, 0xad, 0x98, 0x46, 0x92, 0x2e,
+  0x4f, 0xca, 0xf1, 0x87, 0x43, 0xc1, 0x16, 0x95, 0x57, 0x2d, 0x50, 0xef,
+  0x89, 0x2d, 0x80, 0x7a, 0x57, 0xad, 0xf2, 0xee, 0x5f, 0x6b, 0xd2, 0x00,
+  0x8d, 0xb9, 0x14, 0xf8, 0x14, 0x15, 0x35, 0xd9, 0xc0, 0x46, 0xa3, 0x7b,
+  0x72, 0xc8, 0x91, 0xbf, 0xc9, 0x55, 0x2b, 0xcd, 0xd0, 0x97, 0x3e, 0x9c,
+  0x26, 0x64, 0xcc, 0xdf, 0xce, 0x83, 0x19, 0x71, 0xca, 0x4e, 0xe6, 0xd4,
+  0xd5, 0x7b, 0xa9, 0x19, 0xcd, 0x55, 0xde, 0xc8, 0xec, 0xd2, 0x5e, 0x38,
+  0x53, 0xe5, 0x5c, 0x4f, 0x8c, 0x2d, 0xfe, 0x50, 0x23, 0x36, 0xfc, 0x66,
+  0xe6, 0xcb, 0x8e, 0xa4, 0x39, 0x19, 0x00, 0xb7, 0x95, 0x02, 0x39, 0x91,
+  0x0b, 0x0e, 0xfe, 0x38, 0x2e, 0xd1, 0x1d, 0x05, 0x9a, 0xf6, 0x4d, 0x3e,
+  0x6f, 0x0f, 0x07, 0x1d, 0xaf, 0x2c, 0x1e, 0x8f, 0x60, 0x39, 0xe2, 0xfa,
+  0x36, 0x53, 0x13, 0x39, 0xd4, 0x5e, 0x26, 0x2b, 0xdb, 0x3d, 0xa8, 0x14,
+  0xbd, 0x32, 0xeb, 0x18, 0x03, 0x28, 0x52, 0x04, 0x71, 0xe5, 0xab, 0x33,
+  0x3d, 0xe1, 0x38, 0xbb, 0x07, 0x36, 0x84, 0x62, 0x9c, 0x79, 0xea, 0x16,
+  0x30, 0xf4, 0x5f, 0xc0, 0x2b, 0xe8, 0x71, 0x6b, 0xe4, 0xf9, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x81, 0xf0, 0x30, 0x81, 0xed, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6,
+  0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10,
+  0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb,
+  0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc,
+  0x4e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+  0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0,
+  0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65,
+  0x63, 0x75, 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4e,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+  0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+  0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+  0x00, 0x76, 0xe1, 0x12, 0x6e, 0x4e, 0x4b, 0x16, 0x12, 0x86, 0x30, 0x06,
+  0xb2, 0x81, 0x08, 0xcf, 0xf0, 0x08, 0xc7, 0xc7, 0x71, 0x7e, 0x66, 0xee,
+  0xc2, 0xed, 0xd4, 0x3b, 0x1f, 0xff, 0xf0, 0xf0, 0xc8, 0x4e, 0xd6, 0x43,
+  0x38, 0xb0, 0xb9, 0x30, 0x7d, 0x18, 0xd0, 0x55, 0x83, 0xa2, 0x6a, 0xcb,
+  0x36, 0x11, 0x9c, 0xe8, 0x48, 0x66, 0xa3, 0x6d, 0x7f, 0xb8, 0x13, 0xd4,
+  0x47, 0xfe, 0x8b, 0x5a, 0x5c, 0x73, 0xfc, 0xae, 0xd9, 0x1b, 0x32, 0x19,
+  0x38, 0xab, 0x97, 0x34, 0x14, 0xaa, 0x96, 0xd2, 0xeb, 0xa3, 0x1c, 0x14,
+  0x08, 0x49, 0xb6, 0xbb, 0xe5, 0x91, 0xef, 0x83, 0x36, 0xeb, 0x1d, 0x56,
+  0x6f, 0xca, 0xda, 0xbc, 0x73, 0x63, 0x90, 0xe4, 0x7f, 0x7b, 0x3e, 0x22,
+  0xcb, 0x3d, 0x07, 0xed, 0x5f, 0x38, 0x74, 0x9c, 0xe3, 0x03, 0x50, 0x4e,
+  0xa1, 0xaf, 0x98, 0xee, 0x61, 0xf2, 0x84, 0x3f, 0x12,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            46:fc:eb:ba:b4:d0:2f:0f:92:60:98:23:3f:93:07:8f
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+        Validity
+            Not Before: Apr 17 00:00:00 1997 GMT
+            Not After : Oct 24 23:59:59 2016 GMT
+        Subject: O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign International Server CA - Class 3, OU=www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD.(c)97 VeriSign
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:d8:82:80:e8:d6:19:02:7d:1f:85:18:39:25:a2:
+                    65:2b:e1:bf:d4:05:d3:bc:e6:36:3b:aa:f0:4c:6c:
+                    5b:b6:e7:aa:3c:73:45:55:b2:f1:bd:ea:97:42:ed:
+                    9a:34:0a:15:d4:a9:5c:f5:40:25:dd:d9:07:c1:32:
+                    b2:75:6c:c4:ca:bb:a3:fe:56:27:71:43:aa:63:f5:
+                    30:3e:93:28:e5:fa:f1:09:3b:f3:b7:4d:4e:39:f7:
+                    5c:49:5a:b8:c1:1d:d3:b2:8a:fe:70:30:95:42:cb:
+                    fe:2b:51:8b:5a:3c:3a:f9:22:4f:90:b2:02:a7:53:
+                    9c:4f:34:e7:ab:04:b2:7b:6f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.113733.1.7.1.1
+                  CPS: https://www.verisign.com/CPS
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+            X509v3 Key Usage: 
+                Certificate Sign, CRL Sign
+            Netscape Cert Type: 
+                SSL CA, S/MIME CA
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         40:8e:49:97:96:8a:73:dd:8e:4d:ef:3e:61:b7:ca:a0:62:ad:
+         f4:0e:0a:bb:75:3d:e2:6e:d8:2c:c7:bf:f4:b9:8c:36:9b:ca:
+         a2:d0:9c:72:46:39:f6:a6:82:03:65:11:c4:bc:bf:2d:a6:f5:
+         d9:3b:0a:b5:98:fa:b3:78:b9:1e:f2:2b:4c:62:d5:fd:b2:7a:
+         1d:df:33:fd:73:f9:a5:d8:2d:8c:2a:ea:d1:fc:b0:28:b6:e9:
+         49:48:13:4b:83:8a:1b:48:7b:24:f7:38:de:6f:41:54:b8:ab:
+         57:6b:06:df:c7:a2:d4:a9:f6:f1:36:62:80:88:f2:8b:75:d6:
+         80:71
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIQRvzrurTQLw+SYJgjP5MHjzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNOTcwNDE3MDAwMDAwWhcNMTYxMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
+BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
+MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
+TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
+veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
+OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
+4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
+KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
+HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
+ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
+oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
+BQUAA4GBAECOSZeWinPdjk3vPmG3yqBirfQOCrt1PeJu2CzHv/S5jDabyqLQnHJG
+OfamggNlEcS8vy2m9dk7CrWY+rN4uR7yK0xi1f2yeh3fM/1z+aXYLYwq6tH8sCi2
+6UlIE0uDihtIeyT3ON5vQVS4q1drBt/HotSp9vE2YoCI8ot11oBx
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert2[] = {
+  0x30, 0x82, 0x03, 0x83, 0x30, 0x82, 0x02, 0xec, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x46, 0xfc, 0xeb, 0xba, 0xb4, 0xd0, 0x2f, 0x0f, 0x92,
+  0x60, 0x98, 0x23, 0x3f, 0x93, 0x07, 0x8f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x1e, 0x17, 0x0d, 0x39, 0x37, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x30, 0x32, 0x34,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x1f,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x16, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69,
+  0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x56, 0x65, 0x72, 0x69, 0x53,
+  0x69, 0x67, 0x6e, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+  0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x31, 0x49, 0x30, 0x47, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x40,
+  0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x49, 0x6e, 0x63,
+  0x6f, 0x72, 0x70, 0x2e, 0x62, 0x79, 0x20, 0x52, 0x65, 0x66, 0x2e, 0x20,
+  0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x4c, 0x54,
+  0x44, 0x2e, 0x28, 0x63, 0x29, 0x39, 0x37, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81,
+  0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd8, 0x82, 0x80,
+  0xe8, 0xd6, 0x19, 0x02, 0x7d, 0x1f, 0x85, 0x18, 0x39, 0x25, 0xa2, 0x65,
+  0x2b, 0xe1, 0xbf, 0xd4, 0x05, 0xd3, 0xbc, 0xe6, 0x36, 0x3b, 0xaa, 0xf0,
+  0x4c, 0x6c, 0x5b, 0xb6, 0xe7, 0xaa, 0x3c, 0x73, 0x45, 0x55, 0xb2, 0xf1,
+  0xbd, 0xea, 0x97, 0x42, 0xed, 0x9a, 0x34, 0x0a, 0x15, 0xd4, 0xa9, 0x5c,
+  0xf5, 0x40, 0x25, 0xdd, 0xd9, 0x07, 0xc1, 0x32, 0xb2, 0x75, 0x6c, 0xc4,
+  0xca, 0xbb, 0xa3, 0xfe, 0x56, 0x27, 0x71, 0x43, 0xaa, 0x63, 0xf5, 0x30,
+  0x3e, 0x93, 0x28, 0xe5, 0xfa, 0xf1, 0x09, 0x3b, 0xf3, 0xb7, 0x4d, 0x4e,
+  0x39, 0xf7, 0x5c, 0x49, 0x5a, 0xb8, 0xc1, 0x1d, 0xd3, 0xb2, 0x8a, 0xfe,
+  0x70, 0x30, 0x95, 0x42, 0xcb, 0xfe, 0x2b, 0x51, 0x8b, 0x5a, 0x3c, 0x3a,
+  0xf9, 0x22, 0x4f, 0x90, 0xb2, 0x02, 0xa7, 0x53, 0x9c, 0x4f, 0x34, 0xe7,
+  0xab, 0x04, 0xb2, 0x7b, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+  0xe3, 0x30, 0x81, 0xe0, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04,
+  0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x44, 0x06,
+  0x03, 0x55, 0x1d, 0x20, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b,
+  0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x01, 0x01, 0x30,
+  0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+  0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x34, 0x06, 0x03, 0x55,
+  0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+  0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08,
+  0x01, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x06, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+  0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x31, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24,
+  0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x40, 0x8e, 0x49, 0x97, 0x96,
+  0x8a, 0x73, 0xdd, 0x8e, 0x4d, 0xef, 0x3e, 0x61, 0xb7, 0xca, 0xa0, 0x62,
+  0xad, 0xf4, 0x0e, 0x0a, 0xbb, 0x75, 0x3d, 0xe2, 0x6e, 0xd8, 0x2c, 0xc7,
+  0xbf, 0xf4, 0xb9, 0x8c, 0x36, 0x9b, 0xca, 0xa2, 0xd0, 0x9c, 0x72, 0x46,
+  0x39, 0xf6, 0xa6, 0x82, 0x03, 0x65, 0x11, 0xc4, 0xbc, 0xbf, 0x2d, 0xa6,
+  0xf5, 0xd9, 0x3b, 0x0a, 0xb5, 0x98, 0xfa, 0xb3, 0x78, 0xb9, 0x1e, 0xf2,
+  0x2b, 0x4c, 0x62, 0xd5, 0xfd, 0xb2, 0x7a, 0x1d, 0xdf, 0x33, 0xfd, 0x73,
+  0xf9, 0xa5, 0xd8, 0x2d, 0x8c, 0x2a, 0xea, 0xd1, 0xfc, 0xb0, 0x28, 0xb6,
+  0xe9, 0x49, 0x48, 0x13, 0x4b, 0x83, 0x8a, 0x1b, 0x48, 0x7b, 0x24, 0xf7,
+  0x38, 0xde, 0x6f, 0x41, 0x54, 0xb8, 0xab, 0x57, 0x6b, 0x06, 0xdf, 0xc7,
+  0xa2, 0xd4, 0xa9, 0xf6, 0xf1, 0x36, 0x62, 0x80, 0x88, 0xf2, 0x8b, 0x75,
+  0xd6, 0x80, 0x71,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 429597 (0x68e1d)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+        Validity
+            Not Before: Nov 28 16:08:31 2006 GMT
+            Not After : Aug 21 15:08:31 2018 GMT
+        Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:be:b8:15:7b:ff:d4:7c:7d:67:ad:83:64:7b:c8:
+                    42:53:2d:df:f6:84:08:20:61:d6:01:59:6a:9c:44:
+                    11:af:ef:76:fd:95:7e:ce:61:30:bb:7a:83:5f:02:
+                    bd:01:66:ca:ee:15:8d:6f:a1:30:9c:bd:a1:85:9e:
+                    94:3a:f3:56:88:00:31:cf:d8:ee:6a:96:02:d9:ed:
+                    03:8c:fb:75:6d:e7:ea:b8:55:16:05:16:9a:f4:e0:
+                    5e:b1:88:c0:64:85:5c:15:4d:88:c7:b7:ba:e0:75:
+                    e9:ad:05:3d:9d:c7:89:48:e0:bb:28:c8:03:e1:30:
+                    93:64:5e:52:c0:59:70:22:35:57:88:8a:f1:95:0a:
+                    83:d7:bc:31:73:01:34:ed:ef:46:71:e0:6b:02:a8:
+                    35:72:6b:97:9b:66:e0:cb:1c:79:5f:d8:1a:04:68:
+                    1e:47:02:e6:9d:60:e2:36:97:01:df:ce:35:92:df:
+                    be:67:c7:6d:77:59:3b:8f:9d:d6:90:15:94:bc:42:
+                    34:10:c1:39:f9:b1:27:3e:7e:d6:8a:75:c5:b2:af:
+                    96:d3:a2:de:9b:e4:98:be:7d:e1:e9:81:ad:b6:6f:
+                    fc:d7:0e:da:e0:34:b0:0d:1a:77:e7:e3:08:98:ef:
+                    58:fa:9c:84:b7:36:af:c2:df:ac:d2:f4:10:06:70:
+                    71:35
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+            X509v3 Authority Key Identifier: 
+                keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/secureca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.geotrust.com/resources/cps
+
+    Signature Algorithm: sha1WithRSAEncryption
+         7b:60:06:e9:dd:a7:1d:29:08:ef:11:f9:d5:3b:3c:d2:2b:53:
+         cb:3e:ed:be:76:60:64:48:a0:e6:cb:e8:49:c3:1a:bf:dd:ad:
+         c5:4c:bd:53:48:55:41:db:18:b1:4e:3b:3a:68:2c:24:5a:41:
+         f5:c8:a9:44:a6:32:29:2d:75:f8:4d:f2:50:8e:f0:e2:9b:e9:
+         e1:e4:3b:70:b7:32:89:db:a8:39:c5:5b:68:56:bd:04:15:c3:
+         b6:cb:1b:24:4a:a7:fc:c4:d5:8d:b6:98:dd:03:f6:b1:b3:94:
+         da:3f:52:a0:a4:50:06:ca:45:67:4e:ff:f1:41:89:40:00:36:
+         7e:79
+-----BEGIN CERTIFICATE-----
+MIIDizCCAvSgAwIBAgIDBo4dMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMTI4MTYwODMxWhcNMTgwODIxMTUwODMx
+WjBYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UE
+AxMoR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64FXv/1Hx9Z62DZHvIQlMt3/aE
+CCBh1gFZapxEEa/vdv2Vfs5hMLt6g18CvQFmyu4VjW+hMJy9oYWelDrzVogAMc/Y
+7mqWAtntA4z7dW3n6rhVFgUWmvTgXrGIwGSFXBVNiMe3uuB16a0FPZ3HiUjguyjI
+A+Ewk2ReUsBZcCI1V4iK8ZUKg9e8MXMBNO3vRnHgawKoNXJrl5tm4MsceV/YGgRo
+HkcC5p1g4jaXAd/ONZLfvmfHbXdZO4+d1pAVlLxCNBDBOfmxJz5+1op1xbKvltOi
+3pvkmL594emBrbZv/NcO2uA0sA0ad+fjCJjvWPqchLc2r8LfrNL0EAZwcTUCAwEA
+AaOB6DCB5TAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCzVUEGXFYvwjzZhW0r7
+a9mZyTOSMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMA8GA1UdEwEB
+/wQFMAMBAf8wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9zZWN1cmVjYS5jcmwwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJ
+KoZIhvcNAQEFBQADgYEAe2AG6d2nHSkI7xH51Ts80itTyz7tvnZgZEig5svoScMa
+v92txUy9U0hVQdsYsU47OmgsJFpB9cipRKYyKS11+E3yUI7w4pvp4eQ7cLcyiduo
+OcVbaFa9BBXDtssbJEqn/MTVjbaY3QP2sbOU2j9SoKRQBspFZ07/8UGJQAA2fnk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert3[] = {
+  0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x06, 0x8e, 0x1d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+  0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31,
+  0x31, 0x32, 0x38, 0x31, 0x36, 0x30, 0x38, 0x33, 0x31, 0x5a, 0x17, 0x0d,
+  0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x35, 0x30, 0x38, 0x33, 0x31,
+  0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d,
+  0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84,
+  0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef,
+  0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02,
+  0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd,
+  0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8,
+  0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7,
+  0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88,
+  0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75,
+  0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8,
+  0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35,
+  0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01,
+  0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b,
+  0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68,
+  0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce,
+  0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d,
+  0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1,
+  0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2,
+  0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f,
+  0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3,
+  0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf,
+  0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5,
+  0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb,
+  0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b,
+  0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98,
+  0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+  0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75,
+  0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x81, 0x81, 0x00, 0x7b, 0x60, 0x06, 0xe9, 0xdd, 0xa7, 0x1d, 0x29, 0x08,
+  0xef, 0x11, 0xf9, 0xd5, 0x3b, 0x3c, 0xd2, 0x2b, 0x53, 0xcb, 0x3e, 0xed,
+  0xbe, 0x76, 0x60, 0x64, 0x48, 0xa0, 0xe6, 0xcb, 0xe8, 0x49, 0xc3, 0x1a,
+  0xbf, 0xdd, 0xad, 0xc5, 0x4c, 0xbd, 0x53, 0x48, 0x55, 0x41, 0xdb, 0x18,
+  0xb1, 0x4e, 0x3b, 0x3a, 0x68, 0x2c, 0x24, 0x5a, 0x41, 0xf5, 0xc8, 0xa9,
+  0x44, 0xa6, 0x32, 0x29, 0x2d, 0x75, 0xf8, 0x4d, 0xf2, 0x50, 0x8e, 0xf0,
+  0xe2, 0x9b, 0xe9, 0xe1, 0xe4, 0x3b, 0x70, 0xb7, 0x32, 0x89, 0xdb, 0xa8,
+  0x39, 0xc5, 0x5b, 0x68, 0x56, 0xbd, 0x04, 0x15, 0xc3, 0xb6, 0xcb, 0x1b,
+  0x24, 0x4a, 0xa7, 0xfc, 0xc4, 0xd5, 0x8d, 0xb6, 0x98, 0xdd, 0x03, 0xf6,
+  0xb1, 0xb3, 0x94, 0xda, 0x3f, 0x52, 0xa0, 0xa4, 0x50, 0x06, 0xca, 0x45,
+  0x67, 0x4e, 0xff, 0xf1, 0x41, 0x89, 0x40, 0x00, 0x36, 0x7e, 0x79,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 880226 (0xd6e62)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+        Validity
+            Not Before: Nov 27 00:00:00 2006 GMT
+            Not After : Aug 21 16:15:00 2018 GMT
+        Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:be:b8:15:7b:ff:d4:7c:7d:67:ad:83:64:7b:c8:
+                    42:53:2d:df:f6:84:08:20:61:d6:01:59:6a:9c:44:
+                    11:af:ef:76:fd:95:7e:ce:61:30:bb:7a:83:5f:02:
+                    bd:01:66:ca:ee:15:8d:6f:a1:30:9c:bd:a1:85:9e:
+                    94:3a:f3:56:88:00:31:cf:d8:ee:6a:96:02:d9:ed:
+                    03:8c:fb:75:6d:e7:ea:b8:55:16:05:16:9a:f4:e0:
+                    5e:b1:88:c0:64:85:5c:15:4d:88:c7:b7:ba:e0:75:
+                    e9:ad:05:3d:9d:c7:89:48:e0:bb:28:c8:03:e1:30:
+                    93:64:5e:52:c0:59:70:22:35:57:88:8a:f1:95:0a:
+                    83:d7:bc:31:73:01:34:ed:ef:46:71:e0:6b:02:a8:
+                    35:72:6b:97:9b:66:e0:cb:1c:79:5f:d8:1a:04:68:
+                    1e:47:02:e6:9d:60:e2:36:97:01:df:ce:35:92:df:
+                    be:67:c7:6d:77:59:3b:8f:9d:d6:90:15:94:bc:42:
+                    34:10:c1:39:f9:b1:27:3e:7e:d6:8a:75:c5:b2:af:
+                    96:d3:a2:de:9b:e4:98:be:7d:e1:e9:81:ad:b6:6f:
+                    fc:d7:0e:da:e0:34:b0:0d:1a:77:e7:e3:08:98:ef:
+                    58:fa:9c:84:b7:36:af:c2:df:ac:d2:f4:10:06:70:
+                    71:35
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+            X509v3 Authority Key Identifier: 
+                keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/secureca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.geotrust.com/resources/cps
+
+    Signature Algorithm: sha1WithRSAEncryption
+         af:f3:0e:d6:72:ab:c7:a9:97:ca:2a:6b:84:39:de:79:a9:f0:
+         81:e5:08:67:ab:d7:2f:20:02:01:71:0c:04:22:c9:1e:88:95:
+         03:c9:49:3a:af:67:08:49:b0:d5:08:f5:20:3d:80:91:a0:c5:
+         87:a3:fb:c9:a3:17:91:f9:a8:2f:ae:e9:0f:df:96:72:0f:75:
+         17:80:5d:78:01:4d:9f:1f:6d:7b:d8:f5:42:38:23:1a:99:93:
+         f4:83:be:3b:35:74:e7:37:13:35:7a:ac:b4:b6:90:82:6c:27:
+         a4:e0:ec:9e:35:bd:bf:e5:29:a1:47:9f:5b:32:fc:e9:99:7d:
+         2b:39
+-----BEGIN CERTIFICATE-----
+MIIDizCCAvSgAwIBAgIDDW5iMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMTI3MDAwMDAwWhcNMTgwODIxMTYxNTAw
+WjBYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UE
+AxMoR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64FXv/1Hx9Z62DZHvIQlMt3/aE
+CCBh1gFZapxEEa/vdv2Vfs5hMLt6g18CvQFmyu4VjW+hMJy9oYWelDrzVogAMc/Y
+7mqWAtntA4z7dW3n6rhVFgUWmvTgXrGIwGSFXBVNiMe3uuB16a0FPZ3HiUjguyjI
+A+Ewk2ReUsBZcCI1V4iK8ZUKg9e8MXMBNO3vRnHgawKoNXJrl5tm4MsceV/YGgRo
+HkcC5p1g4jaXAd/ONZLfvmfHbXdZO4+d1pAVlLxCNBDBOfmxJz5+1op1xbKvltOi
+3pvkmL594emBrbZv/NcO2uA0sA0ad+fjCJjvWPqchLc2r8LfrNL0EAZwcTUCAwEA
+AaOB6DCB5TAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCzVUEGXFYvwjzZhW0r7
+a9mZyTOSMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMA8GA1UdEwEB
+/wQFMAMBAf8wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9zZWN1cmVjYS5jcmwwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJ
+KoZIhvcNAQEFBQADgYEAr/MO1nKrx6mXyiprhDneeanwgeUIZ6vXLyACAXEMBCLJ
+HoiVA8lJOq9nCEmw1Qj1ID2AkaDFh6P7yaMXkfmoL67pD9+Wcg91F4BdeAFNnx9t
+e9j1QjgjGpmT9IO+OzV05zcTNXqstLaQgmwnpODsnjW9v+UpoUefWzL86Zl9Kzk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert4[] = {
+  0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x0d, 0x6e, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+  0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31,
+  0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d,
+  0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x36, 0x31, 0x35, 0x30, 0x30,
+  0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d,
+  0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84,
+  0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef,
+  0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02,
+  0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd,
+  0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8,
+  0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7,
+  0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88,
+  0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75,
+  0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8,
+  0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35,
+  0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01,
+  0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b,
+  0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68,
+  0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce,
+  0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d,
+  0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1,
+  0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2,
+  0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f,
+  0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3,
+  0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf,
+  0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5,
+  0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb,
+  0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b,
+  0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98,
+  0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+  0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75,
+  0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x81, 0x81, 0x00, 0xaf, 0xf3, 0x0e, 0xd6, 0x72, 0xab, 0xc7, 0xa9, 0x97,
+  0xca, 0x2a, 0x6b, 0x84, 0x39, 0xde, 0x79, 0xa9, 0xf0, 0x81, 0xe5, 0x08,
+  0x67, 0xab, 0xd7, 0x2f, 0x20, 0x02, 0x01, 0x71, 0x0c, 0x04, 0x22, 0xc9,
+  0x1e, 0x88, 0x95, 0x03, 0xc9, 0x49, 0x3a, 0xaf, 0x67, 0x08, 0x49, 0xb0,
+  0xd5, 0x08, 0xf5, 0x20, 0x3d, 0x80, 0x91, 0xa0, 0xc5, 0x87, 0xa3, 0xfb,
+  0xc9, 0xa3, 0x17, 0x91, 0xf9, 0xa8, 0x2f, 0xae, 0xe9, 0x0f, 0xdf, 0x96,
+  0x72, 0x0f, 0x75, 0x17, 0x80, 0x5d, 0x78, 0x01, 0x4d, 0x9f, 0x1f, 0x6d,
+  0x7b, 0xd8, 0xf5, 0x42, 0x38, 0x23, 0x1a, 0x99, 0x93, 0xf4, 0x83, 0xbe,
+  0x3b, 0x35, 0x74, 0xe7, 0x37, 0x13, 0x35, 0x7a, 0xac, 0xb4, 0xb6, 0x90,
+  0x82, 0x6c, 0x27, 0xa4, 0xe0, 0xec, 0x9e, 0x35, 0xbd, 0xbf, 0xe5, 0x29,
+  0xa1, 0x47, 0x9f, 0x5b, 0x32, 0xfc, 0xe9, 0x99, 0x7d, 0x2b, 0x39,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120020005 (0x7275c25)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Feb 24 20:05:10 2010 GMT
+            Not After : Aug 13 23:59:00 2018 GMT
+        Subject: C=JP, O=Cybertrust Japan Co., Ltd., CN=Cybertrust Japan Public CA G1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:be:bf:b6:ed:e4:f1:4c:02:98:0b:5a:7c:c1:69:
+                    18:20:48:92:03:08:12:d2:52:39:cf:f4:cc:9f:6c:
+                    ea:3a:ab:ff:46:db:f4:dd:e5:31:ed:01:89:90:ac:
+                    44:75:a6:31:5f:a3:41:89:36:97:ab:f4:43:7d:4c:
+                    18:15:5e:73:ca:aa:af:72:ff:4a:98:cc:95:24:5a:
+                    8d:9f:67:98:c6:ce:a1:e6:48:51:f9:8b:3c:b4:32:
+                    82:ec:15:a9:7b:35:a6:87:3e:84:2e:21:4b:6d:d1:
+                    7a:ed:8c:cb:a8:e1:88:0f:1c:75:77:79:45:2b:7b:
+                    cb:6f:bb:08:94:75:fd:5a:c9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                5A:84:4B:BB:97:58:E2:42:E0:8C:AA:9C:A9:BD:62:07:6C:E4:96:AB
+    Signature Algorithm: sha1WithRSAEncryption
+         31:53:e8:7c:8b:09:f4:98:77:0b:07:05:e4:00:3e:f3:4a:af:
+         3c:fe:ea:e0:99:aa:4e:f2:ce:c1:94:af:3d:c0:0b:68:a7:fd:
+         6f:e8:2f:70:6d:22:59:b5:7e:f5:62:c5:36:af:e9:0b:4c:39:
+         8f:80:4e:a4:37:49:69:78:35:32:d6:b5:f9:f9:48:bc:98:16:
+         fc:ff:3e:fa:df:f8:4b:3e:66:72:4f:02:1c:f8:d2:12:f4:bd:
+         4c:ed:56:b0:a2:73:c8:4f:82:ce:0d:b4:c4:af:0e:43:70:6e:
+         08:d4:ec:b0:c1:c4:7f:75:18:99:76:7d:68:d2:13:6c:37:52:
+         34:56
+-----BEGIN CERTIFICATE-----
+MIIDuTCCAyKgAwIBAgIEBydcJTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDIyNDIwMDUxMFoXDTE4MDgxMzIzNTkwMFowWjELMAkG
+A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMSYw
+JAYDVQQDEx1DeWJlcnRydXN0IEphcGFuIFB1YmxpYyBDQSBHMTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAvr+27eTxTAKYC1p8wWkYIEiSAwgS0lI5z/TMn2zq
+Oqv/Rtv03eUx7QGJkKxEdaYxX6NBiTaXq/RDfUwYFV5zyqqvcv9KmMyVJFqNn2eY
+xs6h5khR+Ys8tDKC7BWpezWmhz6ELiFLbdF67YzLqOGIDxx1d3lFK3vLb7sIlHX9
+WskCAwEAAaOCAW8wggFrMBIGA1UdEwEB/wQIMAYBAf8CAQAwUwYDVR0gBEwwSjBI
+BgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYtaHR0cDovL2N5YmVydHJ1c3Qub21u
+aXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4GA1UdDwEB/wQEAwIBBjCBiQYDVR0j
+BIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRp
+b24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDww
+OqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8y
+MDE4L2NkcC5jcmwwHQYDVR0OBBYEFFqES7uXWOJC4IyqnKm9Ygds5JarMA0GCSqG
+SIb3DQEBBQUAA4GBADFT6HyLCfSYdwsHBeQAPvNKrzz+6uCZqk7yzsGUrz3AC2in
+/W/oL3BtIlm1fvVixTav6QtMOY+ATqQ3SWl4NTLWtfn5SLyYFvz/Pvrf+Es+ZnJP
+Ahz40hL0vUztVrCic8hPgs4NtMSvDkNwbgjU7LDBxH91GJl2fWjSE2w3UjRW
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert5[] = {
+  0x30, 0x82, 0x03, 0xb9, 0x30, 0x82, 0x03, 0x22, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x5c, 0x25, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x30, 0x30, 0x32, 0x32, 0x34, 0x32, 0x30, 0x30, 0x35, 0x31,
+  0x30, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x32, 0x33,
+  0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1a, 0x43, 0x79, 0x62, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20,
+  0x43, 0x6f, 0x2e, 0x2c, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x26, 0x30,
+  0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e,
+  0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x47,
+  0x31, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+  0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xbe, 0xbf, 0xb6, 0xed, 0xe4, 0xf1,
+  0x4c, 0x02, 0x98, 0x0b, 0x5a, 0x7c, 0xc1, 0x69, 0x18, 0x20, 0x48, 0x92,
+  0x03, 0x08, 0x12, 0xd2, 0x52, 0x39, 0xcf, 0xf4, 0xcc, 0x9f, 0x6c, 0xea,
+  0x3a, 0xab, 0xff, 0x46, 0xdb, 0xf4, 0xdd, 0xe5, 0x31, 0xed, 0x01, 0x89,
+  0x90, 0xac, 0x44, 0x75, 0xa6, 0x31, 0x5f, 0xa3, 0x41, 0x89, 0x36, 0x97,
+  0xab, 0xf4, 0x43, 0x7d, 0x4c, 0x18, 0x15, 0x5e, 0x73, 0xca, 0xaa, 0xaf,
+  0x72, 0xff, 0x4a, 0x98, 0xcc, 0x95, 0x24, 0x5a, 0x8d, 0x9f, 0x67, 0x98,
+  0xc6, 0xce, 0xa1, 0xe6, 0x48, 0x51, 0xf9, 0x8b, 0x3c, 0xb4, 0x32, 0x82,
+  0xec, 0x15, 0xa9, 0x7b, 0x35, 0xa6, 0x87, 0x3e, 0x84, 0x2e, 0x21, 0x4b,
+  0x6d, 0xd1, 0x7a, 0xed, 0x8c, 0xcb, 0xa8, 0xe1, 0x88, 0x0f, 0x1c, 0x75,
+  0x77, 0x79, 0x45, 0x2b, 0x7b, 0xcb, 0x6f, 0xbb, 0x08, 0x94, 0x75, 0xfd,
+  0x5a, 0xc9, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6f, 0x30,
+  0x82, 0x01, 0x6b, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48,
+  0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30,
+  0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+  0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79,
+  0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e,
+  0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d,
+  0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+  0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+  0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+  0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79,
+  0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5,
+  0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30,
+  0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+  0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+  0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32,
+  0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5a, 0x84,
+  0x4b, 0xbb, 0x97, 0x58, 0xe2, 0x42, 0xe0, 0x8c, 0xaa, 0x9c, 0xa9, 0xbd,
+  0x62, 0x07, 0x6c, 0xe4, 0x96, 0xab, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+  0x00, 0x31, 0x53, 0xe8, 0x7c, 0x8b, 0x09, 0xf4, 0x98, 0x77, 0x0b, 0x07,
+  0x05, 0xe4, 0x00, 0x3e, 0xf3, 0x4a, 0xaf, 0x3c, 0xfe, 0xea, 0xe0, 0x99,
+  0xaa, 0x4e, 0xf2, 0xce, 0xc1, 0x94, 0xaf, 0x3d, 0xc0, 0x0b, 0x68, 0xa7,
+  0xfd, 0x6f, 0xe8, 0x2f, 0x70, 0x6d, 0x22, 0x59, 0xb5, 0x7e, 0xf5, 0x62,
+  0xc5, 0x36, 0xaf, 0xe9, 0x0b, 0x4c, 0x39, 0x8f, 0x80, 0x4e, 0xa4, 0x37,
+  0x49, 0x69, 0x78, 0x35, 0x32, 0xd6, 0xb5, 0xf9, 0xf9, 0x48, 0xbc, 0x98,
+  0x16, 0xfc, 0xff, 0x3e, 0xfa, 0xdf, 0xf8, 0x4b, 0x3e, 0x66, 0x72, 0x4f,
+  0x02, 0x1c, 0xf8, 0xd2, 0x12, 0xf4, 0xbd, 0x4c, 0xed, 0x56, 0xb0, 0xa2,
+  0x73, 0xc8, 0x4f, 0x82, 0xce, 0x0d, 0xb4, 0xc4, 0xaf, 0x0e, 0x43, 0x70,
+  0x6e, 0x08, 0xd4, 0xec, 0xb0, 0xc1, 0xc4, 0x7f, 0x75, 0x18, 0x99, 0x76,
+  0x7d, 0x68, 0xd2, 0x13, 0x6c, 0x37, 0x52, 0x34, 0x56,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 67109891 (0x4000403)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: May 11 15:32:00 2006 GMT
+            Not After : May 11 23:59:00 2013 GMT
+        Subject: C=US, O=Akamai Technologies Inc, CN=Akamai Subordinate CA 3
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:9d:34:76:73:b3:26:44:c4:60:cc:76:5f:8f:d8:
+                    2f:4b:3a:12:56:8c:6d:d5:b4:e2:ac:0c:e1:47:8a:
+                    85:43:12:bc:03:66:85:20:1d:6b:8a:74:72:38:85:
+                    61:a9:73:0b:57:5b:db:c5:9e:b3:66:c5:51:f8:0a:
+                    90:7c:f8:74:14:72:12:80:f4:e8:5a:cd:c8:bb:11:
+                    14:c9:44:2f:ec:e1:af:33:c1:59:29:dd:4c:85:7b:
+                    1c:80:dd:46:a5:64:cf:60:ef:4f:55:93:3e:05:a9:
+                    16:24:2b:48:ff:9f:05:92:de:0c:e7:9f:60:df:54:
+                    6f:a7:16:ee:ff:af:61:a9:9d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                BE:39:BF:41:66:FA:D4:CE:8B:6E:78:A3:49:7E:DE:3D:C4:2E:2B:F6
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://www.public-trust.com/CPS/OmniRoot.html
+
+            X509v3 Authority Key Identifier: 
+                keyid:A6:0C:1D:9F:61:FF:07:17:B5:BF:38:46:DB:43:30:D5:8E:B0:52:06
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 Key Usage: critical
+                Digital Signature, Non Repudiation, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+    Signature Algorithm: sha1WithRSAEncryption
+         76:87:d3:ae:4d:3d:c4:6b:28:e1:52:1f:79:81:1e:e9:62:1a:
+         f7:4f:d9:1a:c0:e5:05:11:fa:77:f9:ff:b1:25:17:5e:ca:19:
+         c8:ac:cc:dc:71:95:ce:cf:66:02:60:c1:7e:ff:ec:d9:b6:70:
+         e1:03:60:33:43:0c:36:55:8d:30:97:5d:5d:97:09:6d:9d:78:
+         33:a5:56:84:a6:28:b8:a1:19:9d:a0:2c:48:27:be:5c:7b:05:
+         d2:16:94:7c:e9:f1:a6:3e:29:ec:26:63:fc:39:c6:65:50:7c:
+         52:1f:76:39:16:b4:97:26:39:ab:8e:1d:fd:b5:7a:c0:3a:1d:
+         3b:7f
+-----BEGIN CERTIFICATE-----
+MIIDxzCCAzCgAwIBAgIEBAAEAzANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA2MDUxMTE1MzIwMFoXDTEzMDUxMTIzNTkwMFowUTELMAkG
+A1UEBhMCVVMxIDAeBgNVBAoTF0FrYW1haSBUZWNobm9sb2dpZXMgSW5jMSAwHgYD
+VQQDExdBa2FtYWkgU3Vib3JkaW5hdGUgQ0EgMzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEAnTR2c7MmRMRgzHZfj9gvSzoSVoxt1bTirAzhR4qFQxK8A2aFIB1r
+inRyOIVhqXMLV1vbxZ6zZsVR+AqQfPh0FHISgPToWs3IuxEUyUQv7OGvM8FZKd1M
+hXscgN1GpWTPYO9PVZM+BakWJCtI/58Fkt4M559g31Rvpxbu/69hqZ0CAwEAAaOC
+AYYwggGCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0
+LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFL45v0Fm+tTO
+i254o0l+3j3ELiv2MFMGA1UdIARMMEowSAYJKwYBBAGxPgEAMDswOQYIKwYBBQUH
+AgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMvT21uaVJvb3QuaHRt
+bDCBoAYDVR0jBIGYMIGVgBSmDB2fYf8HF7W/OEbbQzDVjrBSBqF5pHcwdTELMAkG
+A1UEBhMCVVMxGDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEnMCUGA1UECxMeR1RF
+IEN5YmVyVHJ1c3QgU29sdXRpb25zLCBJbmMuMSMwIQYDVQQDExpHVEUgQ3liZXJU
+cnVzdCBHbG9iYWwgUm9vdIICAaUwDgYDVR0PAQH/BAQDAgHGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwDQYJKoZIhvcNAQEFBQADgYEAdofTrk09xGso4VIfeYEe6WIa90/Z
+GsDlBRH6d/n/sSUXXsoZyKzM3HGVzs9mAmDBfv/s2bZw4QNgM0MMNlWNMJddXZcJ
+bZ14M6VWhKYouKEZnaAsSCe+XHsF0haUfOnxpj4p7CZj/DnGZVB8Uh92ORa0lyY5
+q44d/bV6wDodO38=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert6[] = {
+  0x30, 0x82, 0x03, 0xc7, 0x30, 0x82, 0x03, 0x30, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x04, 0x00, 0x04, 0x03, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x30, 0x36, 0x30, 0x35, 0x31, 0x31, 0x31, 0x35, 0x33, 0x32, 0x30,
+  0x30, 0x5a, 0x17, 0x0d, 0x31, 0x33, 0x30, 0x35, 0x31, 0x31, 0x32, 0x33,
+  0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x51, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x20, 0x30, 0x1e,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x17, 0x41, 0x6b, 0x61, 0x6d, 0x61,
+  0x69, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69,
+  0x65, 0x73, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x17, 0x41, 0x6b, 0x61, 0x6d, 0x61, 0x69, 0x20,
+  0x53, 0x75, 0x62, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x20,
+  0x43, 0x41, 0x20, 0x33, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81,
+  0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0x9d, 0x34, 0x76,
+  0x73, 0xb3, 0x26, 0x44, 0xc4, 0x60, 0xcc, 0x76, 0x5f, 0x8f, 0xd8, 0x2f,
+  0x4b, 0x3a, 0x12, 0x56, 0x8c, 0x6d, 0xd5, 0xb4, 0xe2, 0xac, 0x0c, 0xe1,
+  0x47, 0x8a, 0x85, 0x43, 0x12, 0xbc, 0x03, 0x66, 0x85, 0x20, 0x1d, 0x6b,
+  0x8a, 0x74, 0x72, 0x38, 0x85, 0x61, 0xa9, 0x73, 0x0b, 0x57, 0x5b, 0xdb,
+  0xc5, 0x9e, 0xb3, 0x66, 0xc5, 0x51, 0xf8, 0x0a, 0x90, 0x7c, 0xf8, 0x74,
+  0x14, 0x72, 0x12, 0x80, 0xf4, 0xe8, 0x5a, 0xcd, 0xc8, 0xbb, 0x11, 0x14,
+  0xc9, 0x44, 0x2f, 0xec, 0xe1, 0xaf, 0x33, 0xc1, 0x59, 0x29, 0xdd, 0x4c,
+  0x85, 0x7b, 0x1c, 0x80, 0xdd, 0x46, 0xa5, 0x64, 0xcf, 0x60, 0xef, 0x4f,
+  0x55, 0x93, 0x3e, 0x05, 0xa9, 0x16, 0x24, 0x2b, 0x48, 0xff, 0x9f, 0x05,
+  0x92, 0xde, 0x0c, 0xe7, 0x9f, 0x60, 0xdf, 0x54, 0x6f, 0xa7, 0x16, 0xee,
+  0xff, 0xaf, 0x61, 0xa9, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0x86, 0x30, 0x82, 0x01, 0x82, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+  0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+  0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+  0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+  0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0xbe, 0x39, 0xbf, 0x41, 0x66, 0xfa, 0xd4, 0xce,
+  0x8b, 0x6e, 0x78, 0xa3, 0x49, 0x7e, 0xde, 0x3d, 0xc4, 0x2e, 0x2b, 0xf6,
+  0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30,
+  0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+  0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x2f,
+  0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x68, 0x74, 0x6d,
+  0x6c, 0x30, 0x81, 0xa0, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x98,
+  0x30, 0x81, 0x95, 0x80, 0x14, 0xa6, 0x0c, 0x1d, 0x9f, 0x61, 0xff, 0x07,
+  0x17, 0xb5, 0xbf, 0x38, 0x46, 0xdb, 0x43, 0x30, 0xd5, 0x8e, 0xb0, 0x52,
+  0x06, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27,
+  0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45,
+  0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20,
+  0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0xc6,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x81, 0x81, 0x00, 0x76, 0x87, 0xd3, 0xae, 0x4d, 0x3d, 0xc4, 0x6b, 0x28,
+  0xe1, 0x52, 0x1f, 0x79, 0x81, 0x1e, 0xe9, 0x62, 0x1a, 0xf7, 0x4f, 0xd9,
+  0x1a, 0xc0, 0xe5, 0x05, 0x11, 0xfa, 0x77, 0xf9, 0xff, 0xb1, 0x25, 0x17,
+  0x5e, 0xca, 0x19, 0xc8, 0xac, 0xcc, 0xdc, 0x71, 0x95, 0xce, 0xcf, 0x66,
+  0x02, 0x60, 0xc1, 0x7e, 0xff, 0xec, 0xd9, 0xb6, 0x70, 0xe1, 0x03, 0x60,
+  0x33, 0x43, 0x0c, 0x36, 0x55, 0x8d, 0x30, 0x97, 0x5d, 0x5d, 0x97, 0x09,
+  0x6d, 0x9d, 0x78, 0x33, 0xa5, 0x56, 0x84, 0xa6, 0x28, 0xb8, 0xa1, 0x19,
+  0x9d, 0xa0, 0x2c, 0x48, 0x27, 0xbe, 0x5c, 0x7b, 0x05, 0xd2, 0x16, 0x94,
+  0x7c, 0xe9, 0xf1, 0xa6, 0x3e, 0x29, 0xec, 0x26, 0x63, 0xfc, 0x39, 0xc6,
+  0x65, 0x50, 0x7c, 0x52, 0x1f, 0x76, 0x39, 0x16, 0xb4, 0x97, 0x26, 0x39,
+  0xab, 0x8e, 0x1d, 0xfd, 0xb5, 0x7a, 0xc0, 0x3a, 0x1d, 0x3b, 0x7f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 145105 (0x236d1)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+        Validity
+            Not Before: Feb 19 22:45:05 2010 GMT
+            Not After : Feb 18 22:45:05 2020 GMT
+        Subject: C=US, O=GeoTrust, Inc., CN=RapidSSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c7:71:f8:56:c7:1e:d9:cc:b5:ad:f6:b4:97:a3:
+                    fb:a1:e6:0b:50:5f:50:aa:3a:da:0f:fc:3d:29:24:
+                    43:c6:10:29:c1:fc:55:40:72:ee:bd:ea:df:9f:b6:
+                    41:f4:48:4b:c8:6e:fe:4f:57:12:8b:5b:fa:92:dd:
+                    5e:e8:ad:f3:f0:1b:b1:7b:4d:fb:cf:fd:d1:e5:f8:
+                    e3:dc:e7:f5:73:7f:df:01:49:cf:8c:56:c1:bd:37:
+                    e3:5b:be:b5:4f:8b:8b:f0:da:4f:c7:e3:dd:55:47:
+                    69:df:f2:5b:7b:07:4f:3d:e5:ac:21:c1:c8:1d:7a:
+                    e8:e7:f6:0f:a1:aa:f5:6f:de:a8:65:4f:10:89:9c:
+                    03:f3:89:7a:a5:5e:01:72:33:ed:a9:e9:5a:1e:79:
+                    f3:87:c8:df:c8:c5:fc:37:c8:9a:9a:d7:b8:76:cc:
+                    b0:3e:e7:fd:e6:54:ea:df:5f:52:41:78:59:57:ad:
+                    f1:12:d6:7f:bc:d5:9f:70:d3:05:6c:fa:a3:7d:67:
+                    58:dd:26:62:1d:31:92:0c:79:79:1c:8e:cf:ca:7b:
+                    c1:66:af:a8:74:48:fb:8e:82:c2:9e:2c:99:5c:7b:
+                    2d:5d:9b:bc:5b:57:9e:7c:3a:7a:13:ad:f2:a3:18:
+                    5b:2b:59:0f:cd:5c:3a:eb:68:33:c6:28:1d:82:d1:
+                    50:8b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                6B:69:3D:6A:18:42:4A:DD:8F:02:65:39:FD:35:24:86:78:91:16:30
+            X509v3 Authority Key Identifier: 
+                keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.geotrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         ab:bc:bc:0a:5d:18:94:e3:c1:b1:c3:a8:4c:55:d6:be:b4:98:
+         f1:ee:3c:1c:cd:cf:f3:24:24:5c:96:03:27:58:fc:36:ae:a2:
+         2f:8f:f1:fe:da:2b:02:c3:33:bd:c8:dd:48:22:2b:60:0f:a5:
+         03:10:fd:77:f8:d0:ed:96:67:4f:fd:ea:47:20:70:54:dc:a9:
+         0c:55:7e:e1:96:25:8a:d9:b5:da:57:4a:be:8d:8e:49:43:63:
+         a5:6c:4e:27:87:25:eb:5b:6d:fe:a2:7f:38:28:e0:36:ab:ad:
+         39:a5:a5:62:c4:b7:5c:58:2c:aa:5d:01:60:a6:62:67:a3:c0:
+         c7:62:23:f4:e7:6c:46:ee:b5:d3:80:6a:22:13:d2:2d:3f:74:
+         4f:ea:af:8c:5f:b4:38:9c:db:ae:ce:af:84:1e:a6:f6:34:51:
+         59:79:d3:e3:75:dc:bc:d7:f3:73:df:92:ec:d2:20:59:6f:9c:
+         fb:95:f8:92:76:18:0a:7c:0f:2c:a6:ca:de:8a:62:7b:d8:f3:
+         ce:5f:68:bd:8f:3e:c1:74:bb:15:72:3a:16:83:a9:0b:e6:4d:
+         99:9c:d8:57:ec:a8:01:51:c7:6f:57:34:5e:ab:4a:2c:42:f6:
+         4f:1c:89:78:de:26:4e:f5:6f:93:4c:15:6b:27:56:4d:00:54:
+         6c:7a:b7:b7
+-----BEGIN CERTIFICATE-----
+MIID1TCCAr2gAwIBAgIDAjbRMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjE5MjI0NTA1WhcNMjAwMjE4MjI0NTA1WjA8MQswCQYDVQQG
+EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xFDASBgNVBAMTC1JhcGlkU1NM
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3H4Vsce2cy1rfa0
+l6P7oeYLUF9QqjraD/w9KSRDxhApwfxVQHLuverfn7ZB9EhLyG7+T1cSi1v6kt1e
+6K3z8Buxe037z/3R5fjj3Of1c3/fAUnPjFbBvTfjW761T4uL8NpPx+PdVUdp3/Jb
+ewdPPeWsIcHIHXro5/YPoar1b96oZU8QiZwD84l6pV4BcjPtqelaHnnzh8jfyMX8
+N8iamte4dsywPuf95lTq319SQXhZV63xEtZ/vNWfcNMFbPqjfWdY3SZiHTGSDHl5
+HI7PynvBZq+odEj7joLCniyZXHstXZu8W1eefDp6E63yoxhbK1kPzVw662gzxigd
+gtFQiwIDAQABo4HZMIHWMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUa2k9ahhC
+St2PAmU5/TUkhniRFjAwHwYDVR0jBBgwFoAUwHqYaI2J+6sFZAwRfap9ZbjKzE4w
+EgYDVR0TAQH/BAgwBgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
+Lmdlb3RydXN0LmNvbS9jcmxzL2d0Z2xvYmFsLmNybDA0BggrBgEFBQcBAQQoMCYw
+JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdlb3RydXN0LmNvbTANBgkqhkiG9w0B
+AQUFAAOCAQEAq7y8Cl0YlOPBscOoTFXWvrSY8e48HM3P8yQkXJYDJ1j8Nq6iL4/x
+/torAsMzvcjdSCIrYA+lAxD9d/jQ7ZZnT/3qRyBwVNypDFV+4ZYlitm12ldKvo2O
+SUNjpWxOJ4cl61tt/qJ/OCjgNqutOaWlYsS3XFgsql0BYKZiZ6PAx2Ij9OdsRu61
+04BqIhPSLT90T+qvjF+0OJzbrs6vhB6m9jRRWXnT43XcvNfzc9+S7NIgWW+c+5X4
+knYYCnwPLKbK3opie9jzzl9ovY8+wXS7FXI6FoOpC+ZNmZzYV+yoAVHHb1c0XqtK
+LEL2TxyJeN4mTvVvk0wVaydWTQBUbHq3tw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert7[] = {
+  0x30, 0x82, 0x03, 0xd5, 0x30, 0x82, 0x02, 0xbd, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x02, 0x36, 0xd1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+  0x32, 0x31, 0x39, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35, 0x5a, 0x17, 0x0d,
+  0x32, 0x30, 0x30, 0x32, 0x31, 0x38, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35,
+  0x5a, 0x30, 0x3c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2c,
+  0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0b, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c,
+  0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xc7, 0x71, 0xf8, 0x56, 0xc7, 0x1e, 0xd9, 0xcc, 0xb5, 0xad, 0xf6, 0xb4,
+  0x97, 0xa3, 0xfb, 0xa1, 0xe6, 0x0b, 0x50, 0x5f, 0x50, 0xaa, 0x3a, 0xda,
+  0x0f, 0xfc, 0x3d, 0x29, 0x24, 0x43, 0xc6, 0x10, 0x29, 0xc1, 0xfc, 0x55,
+  0x40, 0x72, 0xee, 0xbd, 0xea, 0xdf, 0x9f, 0xb6, 0x41, 0xf4, 0x48, 0x4b,
+  0xc8, 0x6e, 0xfe, 0x4f, 0x57, 0x12, 0x8b, 0x5b, 0xfa, 0x92, 0xdd, 0x5e,
+  0xe8, 0xad, 0xf3, 0xf0, 0x1b, 0xb1, 0x7b, 0x4d, 0xfb, 0xcf, 0xfd, 0xd1,
+  0xe5, 0xf8, 0xe3, 0xdc, 0xe7, 0xf5, 0x73, 0x7f, 0xdf, 0x01, 0x49, 0xcf,
+  0x8c, 0x56, 0xc1, 0xbd, 0x37, 0xe3, 0x5b, 0xbe, 0xb5, 0x4f, 0x8b, 0x8b,
+  0xf0, 0xda, 0x4f, 0xc7, 0xe3, 0xdd, 0x55, 0x47, 0x69, 0xdf, 0xf2, 0x5b,
+  0x7b, 0x07, 0x4f, 0x3d, 0xe5, 0xac, 0x21, 0xc1, 0xc8, 0x1d, 0x7a, 0xe8,
+  0xe7, 0xf6, 0x0f, 0xa1, 0xaa, 0xf5, 0x6f, 0xde, 0xa8, 0x65, 0x4f, 0x10,
+  0x89, 0x9c, 0x03, 0xf3, 0x89, 0x7a, 0xa5, 0x5e, 0x01, 0x72, 0x33, 0xed,
+  0xa9, 0xe9, 0x5a, 0x1e, 0x79, 0xf3, 0x87, 0xc8, 0xdf, 0xc8, 0xc5, 0xfc,
+  0x37, 0xc8, 0x9a, 0x9a, 0xd7, 0xb8, 0x76, 0xcc, 0xb0, 0x3e, 0xe7, 0xfd,
+  0xe6, 0x54, 0xea, 0xdf, 0x5f, 0x52, 0x41, 0x78, 0x59, 0x57, 0xad, 0xf1,
+  0x12, 0xd6, 0x7f, 0xbc, 0xd5, 0x9f, 0x70, 0xd3, 0x05, 0x6c, 0xfa, 0xa3,
+  0x7d, 0x67, 0x58, 0xdd, 0x26, 0x62, 0x1d, 0x31, 0x92, 0x0c, 0x79, 0x79,
+  0x1c, 0x8e, 0xcf, 0xca, 0x7b, 0xc1, 0x66, 0xaf, 0xa8, 0x74, 0x48, 0xfb,
+  0x8e, 0x82, 0xc2, 0x9e, 0x2c, 0x99, 0x5c, 0x7b, 0x2d, 0x5d, 0x9b, 0xbc,
+  0x5b, 0x57, 0x9e, 0x7c, 0x3a, 0x7a, 0x13, 0xad, 0xf2, 0xa3, 0x18, 0x5b,
+  0x2b, 0x59, 0x0f, 0xcd, 0x5c, 0x3a, 0xeb, 0x68, 0x33, 0xc6, 0x28, 0x1d,
+  0x82, 0xd1, 0x50, 0x8b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xd9,
+  0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+  0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x6b, 0x69, 0x3d, 0x6a, 0x18, 0x42,
+  0x4a, 0xdd, 0x8f, 0x02, 0x65, 0x39, 0xfd, 0x35, 0x24, 0x86, 0x78, 0x91,
+  0x16, 0x30, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+  0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05,
+  0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b,
+  0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+  0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30,
+  0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+  0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+  0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xab, 0xbc, 0xbc,
+  0x0a, 0x5d, 0x18, 0x94, 0xe3, 0xc1, 0xb1, 0xc3, 0xa8, 0x4c, 0x55, 0xd6,
+  0xbe, 0xb4, 0x98, 0xf1, 0xee, 0x3c, 0x1c, 0xcd, 0xcf, 0xf3, 0x24, 0x24,
+  0x5c, 0x96, 0x03, 0x27, 0x58, 0xfc, 0x36, 0xae, 0xa2, 0x2f, 0x8f, 0xf1,
+  0xfe, 0xda, 0x2b, 0x02, 0xc3, 0x33, 0xbd, 0xc8, 0xdd, 0x48, 0x22, 0x2b,
+  0x60, 0x0f, 0xa5, 0x03, 0x10, 0xfd, 0x77, 0xf8, 0xd0, 0xed, 0x96, 0x67,
+  0x4f, 0xfd, 0xea, 0x47, 0x20, 0x70, 0x54, 0xdc, 0xa9, 0x0c, 0x55, 0x7e,
+  0xe1, 0x96, 0x25, 0x8a, 0xd9, 0xb5, 0xda, 0x57, 0x4a, 0xbe, 0x8d, 0x8e,
+  0x49, 0x43, 0x63, 0xa5, 0x6c, 0x4e, 0x27, 0x87, 0x25, 0xeb, 0x5b, 0x6d,
+  0xfe, 0xa2, 0x7f, 0x38, 0x28, 0xe0, 0x36, 0xab, 0xad, 0x39, 0xa5, 0xa5,
+  0x62, 0xc4, 0xb7, 0x5c, 0x58, 0x2c, 0xaa, 0x5d, 0x01, 0x60, 0xa6, 0x62,
+  0x67, 0xa3, 0xc0, 0xc7, 0x62, 0x23, 0xf4, 0xe7, 0x6c, 0x46, 0xee, 0xb5,
+  0xd3, 0x80, 0x6a, 0x22, 0x13, 0xd2, 0x2d, 0x3f, 0x74, 0x4f, 0xea, 0xaf,
+  0x8c, 0x5f, 0xb4, 0x38, 0x9c, 0xdb, 0xae, 0xce, 0xaf, 0x84, 0x1e, 0xa6,
+  0xf6, 0x34, 0x51, 0x59, 0x79, 0xd3, 0xe3, 0x75, 0xdc, 0xbc, 0xd7, 0xf3,
+  0x73, 0xdf, 0x92, 0xec, 0xd2, 0x20, 0x59, 0x6f, 0x9c, 0xfb, 0x95, 0xf8,
+  0x92, 0x76, 0x18, 0x0a, 0x7c, 0x0f, 0x2c, 0xa6, 0xca, 0xde, 0x8a, 0x62,
+  0x7b, 0xd8, 0xf3, 0xce, 0x5f, 0x68, 0xbd, 0x8f, 0x3e, 0xc1, 0x74, 0xbb,
+  0x15, 0x72, 0x3a, 0x16, 0x83, 0xa9, 0x0b, 0xe6, 0x4d, 0x99, 0x9c, 0xd8,
+  0x57, 0xec, 0xa8, 0x01, 0x51, 0xc7, 0x6f, 0x57, 0x34, 0x5e, 0xab, 0x4a,
+  0x2c, 0x42, 0xf6, 0x4f, 0x1c, 0x89, 0x78, 0xde, 0x26, 0x4e, 0xf5, 0x6f,
+  0x93, 0x4c, 0x15, 0x6b, 0x27, 0x56, 0x4d, 0x00, 0x54, 0x6c, 0x7a, 0xb7,
+  0xb7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 145104 (0x236d0)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+        Validity
+            Not Before: Feb 19 22:39:26 2010 GMT
+            Not After : Feb 18 22:39:26 2020 GMT
+        Subject: C=US, O=GeoTrust, Inc., CN=GeoTrust SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:90:b3:80:c1:e4:e5:46:ad:70:60:3d:ba:e5:14:
+                    dd:9e:8a:5e:8b:75:5a:e6:ca:6d:41:a5:23:e8:39:
+                    85:26:7a:a7:55:77:9a:48:a1:92:7e:3a:1e:1a:f1:
+                    27:ab:a3:4c:39:cc:cb:3d:47:af:81:ae:16:6a:5c:
+                    37:ef:45:41:fd:fb:9a:97:3c:a0:43:9d:c6:df:17:
+                    21:d1:8a:a2:56:c2:03:49:84:12:81:3e:c9:0a:54:
+                    60:66:b9:8c:54:e4:f9:e6:f9:94:f1:e0:5f:75:11:
+                    f2:29:b9:e4:86:a2:b1:89:ad:a6:1e:83:29:63:b2:
+                    f0:54:1c:85:0b:7a:e7:e1:2e:0d:af:a4:bd:cd:e7:
+                    b1:5a:d7:8c:05:5a:0e:4b:73:28:8b:75:5d:34:d8:
+                    77:0b:e1:74:62:e2:71:30:62:d8:bc:8a:05:e5:31:
+                    63:4a:54:89:6a:33:78:a7:4e:55:24:1d:97:ef:1a:
+                    e4:12:c6:0f:30:18:b4:34:4d:e1:d8:23:3b:21:5b:
+                    2d:30:19:25:0e:74:f7:a4:21:4b:a0:a4:20:c9:6c:
+                    cd:98:56:c0:f2:a8:5f:3e:26:75:a0:0d:f8:36:88:
+                    8a:2c:5a:7d:67:30:a9:0f:d1:99:70:2e:78:e1:51:
+                    26:af:55:7a:24:be:8c:39:0d:77:9d:de:02:c3:0c:
+                    bd:1f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                42:79:54:1B:61:CD:55:2B:3E:63:D5:3C:48:57:F5:9F:FB:45:CE:4A
+            X509v3 Authority Key Identifier: 
+                keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.geotrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         d4:ef:53:84:e8:1a:bd:a1:8b:04:c0:a9:f5:5f:a1:10:78:45:
+         5d:b2:57:6a:4e:24:cb:65:4e:31:97:91:9a:d4:24:f8:e2:27:
+         66:70:31:9c:c1:62:54:06:e7:97:1d:3a:9a:c0:a4:29:48:0a:
+         af:24:c7:a8:c4:9a:54:c1:7c:4c:78:4c:2b:68:2c:5d:17:a6:
+         54:78:4c:46:e2:80:c3:1f:38:71:12:d2:d7:53:e3:54:85:50:
+         b8:02:cb:ee:63:3a:f8:56:89:4d:55:bb:2e:c0:c8:18:77:86:
+         31:0b:0b:70:f0:7e:35:83:a4:2a:13:64:56:67:34:5d:16:5f:
+         73:ac:7b:06:24:da:4f:50:6d:2a:ab:d0:4d:53:41:c2:8e:bb:
+         71:03:49:29:86:18:cf:21:42:4c:74:62:51:15:c5:6f:a8:ef:
+         c4:27:e5:1b:33:dd:5a:88:d7:7f:12:d1:a7:61:25:1f:d5:e0:
+         dc:1d:cf:1a:10:d8:a0:cb:5f:8c:fa:0c:e5:bf:71:ff:e5:5d:
+         44:1d:a6:3e:87:47:fa:1a:4e:83:83:12:3f:88:66:95:98:79:
+         9a:85:eb:02:47:cd:25:e3:f2:06:04:4e:99:ca:5c:a0:6e:7a:
+         bb:dd:a3:90:1a:45:33:ef:bf:3e:d2:04:c4:b6:e0:2a:85:65:
+         41:3e:10:d4
+-----BEGIN CERTIFICATE-----
+MIID2TCCAsGgAwIBAgIDAjbQMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjE5MjIzOTI2WhcNMjAwMjE4MjIzOTI2WjBAMQswCQYDVQQG
+EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xGDAWBgNVBAMTD0dlb1RydXN0
+IFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCzgMHk5Uat
+cGA9uuUU3Z6KXot1WubKbUGlI+g5hSZ6p1V3mkihkn46HhrxJ6ujTDnMyz1Hr4Gu
+FmpcN+9FQf37mpc8oEOdxt8XIdGKolbCA0mEEoE+yQpUYGa5jFTk+eb5lPHgX3UR
+8im55IaisYmtph6DKWOy8FQchQt65+EuDa+kvc3nsVrXjAVaDktzKIt1XTTYdwvh
+dGLicTBi2LyKBeUxY0pUiWozeKdOVSQdl+8a5BLGDzAYtDRN4dgjOyFbLTAZJQ50
+96QhS6CkIMlszZhWwPKoXz4mdaAN+DaIiixafWcwqQ/RmXAueOFRJq9VeiS+jDkN
+d53eAsMMvR8CAwEAAaOB2TCB1jAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEJ5
+VBthzVUrPmPVPEhX9Z/7Rc5KMB8GA1UdIwQYMBaAFMB6mGiNifurBWQMEX2qfWW4
+ysxOMBIGA1UdEwEB/wQIMAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDov
+L2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBBQUHAQEE
+KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20wDQYJKoZI
+hvcNAQEFBQADggEBANTvU4ToGr2hiwTAqfVfoRB4RV2yV2pOJMtlTjGXkZrUJPji
+J2ZwMZzBYlQG55cdOprApClICq8kx6jEmlTBfEx4TCtoLF0XplR4TEbigMMfOHES
+0tdT41SFULgCy+5jOvhWiU1Vuy7AyBh3hjELC3DwfjWDpCoTZFZnNF0WX3OsewYk
+2k9QbSqr0E1TQcKOu3EDSSmGGM8hQkx0YlEVxW+o78Qn5Rsz3VqI138S0adhJR/V
+4NwdzxoQ2KDLX4z6DOW/cf/lXUQdpj6HR/oaToODEj+IZpWYeZqF6wJHzSXj8gYE
+TpnKXKBuervdo5AaRTPvvz7SBMS24CqFZUE+ENQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert8[] = {
+  0x30, 0x82, 0x03, 0xd9, 0x30, 0x82, 0x02, 0xc1, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x02, 0x36, 0xd0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+  0x32, 0x31, 0x39, 0x32, 0x32, 0x33, 0x39, 0x32, 0x36, 0x5a, 0x17, 0x0d,
+  0x32, 0x30, 0x30, 0x32, 0x31, 0x38, 0x32, 0x32, 0x33, 0x39, 0x32, 0x36,
+  0x5a, 0x30, 0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2c,
+  0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0f, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0x90, 0xb3, 0x80, 0xc1, 0xe4, 0xe5, 0x46, 0xad,
+  0x70, 0x60, 0x3d, 0xba, 0xe5, 0x14, 0xdd, 0x9e, 0x8a, 0x5e, 0x8b, 0x75,
+  0x5a, 0xe6, 0xca, 0x6d, 0x41, 0xa5, 0x23, 0xe8, 0x39, 0x85, 0x26, 0x7a,
+  0xa7, 0x55, 0x77, 0x9a, 0x48, 0xa1, 0x92, 0x7e, 0x3a, 0x1e, 0x1a, 0xf1,
+  0x27, 0xab, 0xa3, 0x4c, 0x39, 0xcc, 0xcb, 0x3d, 0x47, 0xaf, 0x81, 0xae,
+  0x16, 0x6a, 0x5c, 0x37, 0xef, 0x45, 0x41, 0xfd, 0xfb, 0x9a, 0x97, 0x3c,
+  0xa0, 0x43, 0x9d, 0xc6, 0xdf, 0x17, 0x21, 0xd1, 0x8a, 0xa2, 0x56, 0xc2,
+  0x03, 0x49, 0x84, 0x12, 0x81, 0x3e, 0xc9, 0x0a, 0x54, 0x60, 0x66, 0xb9,
+  0x8c, 0x54, 0xe4, 0xf9, 0xe6, 0xf9, 0x94, 0xf1, 0xe0, 0x5f, 0x75, 0x11,
+  0xf2, 0x29, 0xb9, 0xe4, 0x86, 0xa2, 0xb1, 0x89, 0xad, 0xa6, 0x1e, 0x83,
+  0x29, 0x63, 0xb2, 0xf0, 0x54, 0x1c, 0x85, 0x0b, 0x7a, 0xe7, 0xe1, 0x2e,
+  0x0d, 0xaf, 0xa4, 0xbd, 0xcd, 0xe7, 0xb1, 0x5a, 0xd7, 0x8c, 0x05, 0x5a,
+  0x0e, 0x4b, 0x73, 0x28, 0x8b, 0x75, 0x5d, 0x34, 0xd8, 0x77, 0x0b, 0xe1,
+  0x74, 0x62, 0xe2, 0x71, 0x30, 0x62, 0xd8, 0xbc, 0x8a, 0x05, 0xe5, 0x31,
+  0x63, 0x4a, 0x54, 0x89, 0x6a, 0x33, 0x78, 0xa7, 0x4e, 0x55, 0x24, 0x1d,
+  0x97, 0xef, 0x1a, 0xe4, 0x12, 0xc6, 0x0f, 0x30, 0x18, 0xb4, 0x34, 0x4d,
+  0xe1, 0xd8, 0x23, 0x3b, 0x21, 0x5b, 0x2d, 0x30, 0x19, 0x25, 0x0e, 0x74,
+  0xf7, 0xa4, 0x21, 0x4b, 0xa0, 0xa4, 0x20, 0xc9, 0x6c, 0xcd, 0x98, 0x56,
+  0xc0, 0xf2, 0xa8, 0x5f, 0x3e, 0x26, 0x75, 0xa0, 0x0d, 0xf8, 0x36, 0x88,
+  0x8a, 0x2c, 0x5a, 0x7d, 0x67, 0x30, 0xa9, 0x0f, 0xd1, 0x99, 0x70, 0x2e,
+  0x78, 0xe1, 0x51, 0x26, 0xaf, 0x55, 0x7a, 0x24, 0xbe, 0x8c, 0x39, 0x0d,
+  0x77, 0x9d, 0xde, 0x02, 0xc3, 0x0c, 0xbd, 0x1f, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x81, 0xd9, 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x42, 0x79,
+  0x54, 0x1b, 0x61, 0xcd, 0x55, 0x2b, 0x3e, 0x63, 0xd5, 0x3c, 0x48, 0x57,
+  0xf5, 0x9f, 0xfb, 0x45, 0xce, 0x4a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d,
+  0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8,
+  0xca, 0xcc, 0x4e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f,
+  0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67,
+  0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+  0x00, 0xd4, 0xef, 0x53, 0x84, 0xe8, 0x1a, 0xbd, 0xa1, 0x8b, 0x04, 0xc0,
+  0xa9, 0xf5, 0x5f, 0xa1, 0x10, 0x78, 0x45, 0x5d, 0xb2, 0x57, 0x6a, 0x4e,
+  0x24, 0xcb, 0x65, 0x4e, 0x31, 0x97, 0x91, 0x9a, 0xd4, 0x24, 0xf8, 0xe2,
+  0x27, 0x66, 0x70, 0x31, 0x9c, 0xc1, 0x62, 0x54, 0x06, 0xe7, 0x97, 0x1d,
+  0x3a, 0x9a, 0xc0, 0xa4, 0x29, 0x48, 0x0a, 0xaf, 0x24, 0xc7, 0xa8, 0xc4,
+  0x9a, 0x54, 0xc1, 0x7c, 0x4c, 0x78, 0x4c, 0x2b, 0x68, 0x2c, 0x5d, 0x17,
+  0xa6, 0x54, 0x78, 0x4c, 0x46, 0xe2, 0x80, 0xc3, 0x1f, 0x38, 0x71, 0x12,
+  0xd2, 0xd7, 0x53, 0xe3, 0x54, 0x85, 0x50, 0xb8, 0x02, 0xcb, 0xee, 0x63,
+  0x3a, 0xf8, 0x56, 0x89, 0x4d, 0x55, 0xbb, 0x2e, 0xc0, 0xc8, 0x18, 0x77,
+  0x86, 0x31, 0x0b, 0x0b, 0x70, 0xf0, 0x7e, 0x35, 0x83, 0xa4, 0x2a, 0x13,
+  0x64, 0x56, 0x67, 0x34, 0x5d, 0x16, 0x5f, 0x73, 0xac, 0x7b, 0x06, 0x24,
+  0xda, 0x4f, 0x50, 0x6d, 0x2a, 0xab, 0xd0, 0x4d, 0x53, 0x41, 0xc2, 0x8e,
+  0xbb, 0x71, 0x03, 0x49, 0x29, 0x86, 0x18, 0xcf, 0x21, 0x42, 0x4c, 0x74,
+  0x62, 0x51, 0x15, 0xc5, 0x6f, 0xa8, 0xef, 0xc4, 0x27, 0xe5, 0x1b, 0x33,
+  0xdd, 0x5a, 0x88, 0xd7, 0x7f, 0x12, 0xd1, 0xa7, 0x61, 0x25, 0x1f, 0xd5,
+  0xe0, 0xdc, 0x1d, 0xcf, 0x1a, 0x10, 0xd8, 0xa0, 0xcb, 0x5f, 0x8c, 0xfa,
+  0x0c, 0xe5, 0xbf, 0x71, 0xff, 0xe5, 0x5d, 0x44, 0x1d, 0xa6, 0x3e, 0x87,
+  0x47, 0xfa, 0x1a, 0x4e, 0x83, 0x83, 0x12, 0x3f, 0x88, 0x66, 0x95, 0x98,
+  0x79, 0x9a, 0x85, 0xeb, 0x02, 0x47, 0xcd, 0x25, 0xe3, 0xf2, 0x06, 0x04,
+  0x4e, 0x99, 0xca, 0x5c, 0xa0, 0x6e, 0x7a, 0xbb, 0xdd, 0xa3, 0x90, 0x1a,
+  0x45, 0x33, 0xef, 0xbf, 0x3e, 0xd2, 0x04, 0xc4, 0xb6, 0xe0, 0x2a, 0x85,
+  0x65, 0x41, 0x3e, 0x10, 0xd4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 145106 (0x236d2)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+        Validity
+            Not Before: Feb 26 21:32:31 2010 GMT
+            Not After : Feb 25 21:32:31 2020 GMT
+        Subject: C=US, O=GeoTrust Inc., OU=Domain Validated SSL, CN=GeoTrust DV SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a6:bb:8e:7a:cd:a4:9c:62:57:d4:51:30:42:7b:
+                    8b:1a:b2:d2:88:06:ad:3b:3c:29:13:0c:31:bc:69:
+                    f9:9f:5a:94:da:06:ba:ac:24:04:9e:ce:d4:aa:c4:
+                    48:60:00:f8:34:ae:a1:93:af:de:04:7e:cd:f8:5c:
+                    22:52:0d:56:53:eb:a9:94:cf:fb:74:44:eb:43:94:
+                    a4:97:7a:40:57:35:b6:a4:62:da:d5:48:f8:7a:f1:
+                    ec:90:b5:5f:39:fe:63:72:70:c8:12:85:d0:a5:2e:
+                    86:13:40:6c:eb:6c:4d:d2:54:fd:5f:3e:26:1f:66:
+                    71:a8:c0:b8:85:9e:f5:f5:75:8f:da:91:4e:89:e3:
+                    ca:78:74:30:5f:15:0a:99:a7:ca:83:3a:76:35:48:
+                    d0:dc:8b:1a:22:4e:85:a4:4e:fa:49:6d:2b:70:be:
+                    8e:0c:21:c3:62:cc:a4:d1:ad:16:6b:9a:7b:cb:64:
+                    ff:8d:ba:42:c3:26:aa:15:78:68:9c:ec:f6:6b:c8:
+                    0c:57:0d:e5:38:07:d3:6a:57:03:9d:20:0e:4b:c4:
+                    7b:81:b0:2a:1c:f5:4a:ea:4a:98:49:fe:02:5b:3d:
+                    03:14:90:28:7e:9a:f4:78:d0:31:84:57:e5:4c:38:
+                    7a:42:11:e2:f5:28:51:03:4b:20:15:bb:22:1a:b6:
+                    f0:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                8C:F4:D9:93:0A:47:BC:00:A0:4A:CE:4B:75:6E:A0:B6:B0:B2:7E:FC
+            X509v3 Authority Key Identifier: 
+                keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.geotrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         33:91:37:11:db:40:f9:de:8c:b2:02:88:77:af:63:21:c1:ad:
+         b0:0d:fa:a0:78:56:a3:82:fd:bb:49:5f:14:6d:c8:dc:5f:94:
+         da:11:66:7c:1e:91:c5:b6:d8:6d:4f:aa:f2:bf:21:28:7e:52:
+         a2:92:78:08:61:69:21:fe:2d:ec:82:18:84:f4:d3:8d:c5:8a:
+         bb:8a:cc:5d:e6:a3:b6:cc:6e:ad:6f:b3:0e:61:ee:89:ce:13:
+         34:4f:49:55:f5:39:bb:99:96:f0:f5:ea:5a:3c:9c:16:bd:02:
+         53:f0:2a:0e:41:6e:eb:ef:9e:f7:70:36:cd:80:2a:76:c8:87:
+         e3:eb:23:b3:96:2c:e6:1d:94:5f:1c:a4:e2:cd:24:31:2b:06:
+         38:32:61:61:39:5c:89:4c:48:1d:42:c9:67:9e:d2:bf:58:f7:
+         f9:37:31:b0:67:dd:8d:26:36:1a:78:1a:09:19:3c:93:07:70:
+         2a:e1:7c:29:f5:de:66:57:0b:12:5e:16:ed:5e:bd:37:b3:30:
+         69:c6:92:a5:f6:19:d8:1d:f8:36:12:b9:4b:95:95:9c:d0:ce:
+         6c:30:a7:16:fb:f6:4d:64:b6:5f:2a:14:9c:a6:c8:55:8e:20:
+         f9:65:07:24:cc:38:05:4c:20:88:b4:b5:67:94:cf:5d:8e:62:
+         37:fe:c4:b4
+-----BEGIN CERTIFICATE-----
+MIID+jCCAuKgAwIBAgIDAjbSMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjI2MjEzMjMxWhcNMjAwMjI1MjEzMjMxWjBhMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UECxMURG9tYWluIFZh
+bGlkYXRlZCBTU0wxGzAZBgNVBAMTEkdlb1RydXN0IERWIFNTTCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKa7jnrNpJxiV9RRMEJ7ixqy0ogGrTs8
+KRMMMbxp+Z9alNoGuqwkBJ7O1KrESGAA+DSuoZOv3gR+zfhcIlINVlPrqZTP+3RE
+60OUpJd6QFc1tqRi2tVI+Hrx7JC1Xzn+Y3JwyBKF0KUuhhNAbOtsTdJU/V8+Jh9m
+cajAuIWe9fV1j9qRTonjynh0MF8VCpmnyoM6djVI0NyLGiJOhaRO+kltK3C+jgwh
+w2LMpNGtFmuae8tk/426QsMmqhV4aJzs9mvIDFcN5TgH02pXA50gDkvEe4GwKhz1
+SupKmEn+Als9AxSQKH6a9HjQMYRX5Uw4ekIR4vUoUQNLIBW7Ihq28BUCAwEAAaOB
+2TCB1jAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFIz02ZMKR7wAoErOS3VuoLaw
+sn78MB8GA1UdIwQYMBaAFMB6mGiNifurBWQMEX2qfWW4ysxOMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAB
+hhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBADOR
+NxHbQPnejLICiHevYyHBrbAN+qB4VqOC/btJXxRtyNxflNoRZnwekcW22G1PqvK/
+ISh+UqKSeAhhaSH+LeyCGIT0043FiruKzF3mo7bMbq1vsw5h7onOEzRPSVX1ObuZ
+lvD16lo8nBa9AlPwKg5BbuvvnvdwNs2AKnbIh+PrI7OWLOYdlF8cpOLNJDErBjgy
+YWE5XIlMSB1CyWee0r9Y9/k3MbBn3Y0mNhp4GgkZPJMHcCrhfCn13mZXCxJeFu1e
+vTezMGnGkqX2Gdgd+DYSuUuVlZzQzmwwpxb79k1ktl8qFJymyFWOIPllByTMOAVM
+IIi0tWeUz12OYjf+xLQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert9[] = {
+  0x30, 0x82, 0x03, 0xfa, 0x30, 0x82, 0x02, 0xe2, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x02, 0x36, 0xd2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+  0x32, 0x32, 0x36, 0x32, 0x31, 0x33, 0x32, 0x33, 0x31, 0x5a, 0x17, 0x0d,
+  0x32, 0x30, 0x30, 0x32, 0x32, 0x35, 0x32, 0x31, 0x33, 0x32, 0x33, 0x31,
+  0x5a, 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x14, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61,
+  0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31,
+  0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, 0x65,
+  0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x44, 0x56, 0x20, 0x53, 0x53,
+  0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xa6, 0xbb, 0x8e, 0x7a, 0xcd, 0xa4, 0x9c, 0x62, 0x57, 0xd4, 0x51,
+  0x30, 0x42, 0x7b, 0x8b, 0x1a, 0xb2, 0xd2, 0x88, 0x06, 0xad, 0x3b, 0x3c,
+  0x29, 0x13, 0x0c, 0x31, 0xbc, 0x69, 0xf9, 0x9f, 0x5a, 0x94, 0xda, 0x06,
+  0xba, 0xac, 0x24, 0x04, 0x9e, 0xce, 0xd4, 0xaa, 0xc4, 0x48, 0x60, 0x00,
+  0xf8, 0x34, 0xae, 0xa1, 0x93, 0xaf, 0xde, 0x04, 0x7e, 0xcd, 0xf8, 0x5c,
+  0x22, 0x52, 0x0d, 0x56, 0x53, 0xeb, 0xa9, 0x94, 0xcf, 0xfb, 0x74, 0x44,
+  0xeb, 0x43, 0x94, 0xa4, 0x97, 0x7a, 0x40, 0x57, 0x35, 0xb6, 0xa4, 0x62,
+  0xda, 0xd5, 0x48, 0xf8, 0x7a, 0xf1, 0xec, 0x90, 0xb5, 0x5f, 0x39, 0xfe,
+  0x63, 0x72, 0x70, 0xc8, 0x12, 0x85, 0xd0, 0xa5, 0x2e, 0x86, 0x13, 0x40,
+  0x6c, 0xeb, 0x6c, 0x4d, 0xd2, 0x54, 0xfd, 0x5f, 0x3e, 0x26, 0x1f, 0x66,
+  0x71, 0xa8, 0xc0, 0xb8, 0x85, 0x9e, 0xf5, 0xf5, 0x75, 0x8f, 0xda, 0x91,
+  0x4e, 0x89, 0xe3, 0xca, 0x78, 0x74, 0x30, 0x5f, 0x15, 0x0a, 0x99, 0xa7,
+  0xca, 0x83, 0x3a, 0x76, 0x35, 0x48, 0xd0, 0xdc, 0x8b, 0x1a, 0x22, 0x4e,
+  0x85, 0xa4, 0x4e, 0xfa, 0x49, 0x6d, 0x2b, 0x70, 0xbe, 0x8e, 0x0c, 0x21,
+  0xc3, 0x62, 0xcc, 0xa4, 0xd1, 0xad, 0x16, 0x6b, 0x9a, 0x7b, 0xcb, 0x64,
+  0xff, 0x8d, 0xba, 0x42, 0xc3, 0x26, 0xaa, 0x15, 0x78, 0x68, 0x9c, 0xec,
+  0xf6, 0x6b, 0xc8, 0x0c, 0x57, 0x0d, 0xe5, 0x38, 0x07, 0xd3, 0x6a, 0x57,
+  0x03, 0x9d, 0x20, 0x0e, 0x4b, 0xc4, 0x7b, 0x81, 0xb0, 0x2a, 0x1c, 0xf5,
+  0x4a, 0xea, 0x4a, 0x98, 0x49, 0xfe, 0x02, 0x5b, 0x3d, 0x03, 0x14, 0x90,
+  0x28, 0x7e, 0x9a, 0xf4, 0x78, 0xd0, 0x31, 0x84, 0x57, 0xe5, 0x4c, 0x38,
+  0x7a, 0x42, 0x11, 0xe2, 0xf5, 0x28, 0x51, 0x03, 0x4b, 0x20, 0x15, 0xbb,
+  0x22, 0x1a, 0xb6, 0xf0, 0x15, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+  0xd9, 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x8c, 0xf4, 0xd9, 0x93, 0x0a,
+  0x47, 0xbc, 0x00, 0xa0, 0x4a, 0xce, 0x4b, 0x75, 0x6e, 0xa0, 0xb6, 0xb0,
+  0xb2, 0x7e, 0xfc, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+  0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab,
+  0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+  0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26,
+  0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x33, 0x91,
+  0x37, 0x11, 0xdb, 0x40, 0xf9, 0xde, 0x8c, 0xb2, 0x02, 0x88, 0x77, 0xaf,
+  0x63, 0x21, 0xc1, 0xad, 0xb0, 0x0d, 0xfa, 0xa0, 0x78, 0x56, 0xa3, 0x82,
+  0xfd, 0xbb, 0x49, 0x5f, 0x14, 0x6d, 0xc8, 0xdc, 0x5f, 0x94, 0xda, 0x11,
+  0x66, 0x7c, 0x1e, 0x91, 0xc5, 0xb6, 0xd8, 0x6d, 0x4f, 0xaa, 0xf2, 0xbf,
+  0x21, 0x28, 0x7e, 0x52, 0xa2, 0x92, 0x78, 0x08, 0x61, 0x69, 0x21, 0xfe,
+  0x2d, 0xec, 0x82, 0x18, 0x84, 0xf4, 0xd3, 0x8d, 0xc5, 0x8a, 0xbb, 0x8a,
+  0xcc, 0x5d, 0xe6, 0xa3, 0xb6, 0xcc, 0x6e, 0xad, 0x6f, 0xb3, 0x0e, 0x61,
+  0xee, 0x89, 0xce, 0x13, 0x34, 0x4f, 0x49, 0x55, 0xf5, 0x39, 0xbb, 0x99,
+  0x96, 0xf0, 0xf5, 0xea, 0x5a, 0x3c, 0x9c, 0x16, 0xbd, 0x02, 0x53, 0xf0,
+  0x2a, 0x0e, 0x41, 0x6e, 0xeb, 0xef, 0x9e, 0xf7, 0x70, 0x36, 0xcd, 0x80,
+  0x2a, 0x76, 0xc8, 0x87, 0xe3, 0xeb, 0x23, 0xb3, 0x96, 0x2c, 0xe6, 0x1d,
+  0x94, 0x5f, 0x1c, 0xa4, 0xe2, 0xcd, 0x24, 0x31, 0x2b, 0x06, 0x38, 0x32,
+  0x61, 0x61, 0x39, 0x5c, 0x89, 0x4c, 0x48, 0x1d, 0x42, 0xc9, 0x67, 0x9e,
+  0xd2, 0xbf, 0x58, 0xf7, 0xf9, 0x37, 0x31, 0xb0, 0x67, 0xdd, 0x8d, 0x26,
+  0x36, 0x1a, 0x78, 0x1a, 0x09, 0x19, 0x3c, 0x93, 0x07, 0x70, 0x2a, 0xe1,
+  0x7c, 0x29, 0xf5, 0xde, 0x66, 0x57, 0x0b, 0x12, 0x5e, 0x16, 0xed, 0x5e,
+  0xbd, 0x37, 0xb3, 0x30, 0x69, 0xc6, 0x92, 0xa5, 0xf6, 0x19, 0xd8, 0x1d,
+  0xf8, 0x36, 0x12, 0xb9, 0x4b, 0x95, 0x95, 0x9c, 0xd0, 0xce, 0x6c, 0x30,
+  0xa7, 0x16, 0xfb, 0xf6, 0x4d, 0x64, 0xb6, 0x5f, 0x2a, 0x14, 0x9c, 0xa6,
+  0xc8, 0x55, 0x8e, 0x20, 0xf9, 0x65, 0x07, 0x24, 0xcc, 0x38, 0x05, 0x4c,
+  0x20, 0x88, 0xb4, 0xb5, 0x67, 0x94, 0xcf, 0x5d, 0x8e, 0x62, 0x37, 0xfe,
+  0xc4, 0xb4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 146025 (0x23a69)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+        Validity
+            Not Before: Apr  5 15:15:55 2013 GMT
+            Not After : Apr  4 15:15:55 2015 GMT
+        Subject: C=US, O=Google Inc, CN=Google Internet Authority G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:9c:2a:04:77:5c:d8:50:91:3a:06:a3:82:e0:d8:
+                    50:48:bc:89:3f:f1:19:70:1a:88:46:7e:e0:8f:c5:
+                    f1:89:ce:21:ee:5a:fe:61:0d:b7:32:44:89:a0:74:
+                    0b:53:4f:55:a4:ce:82:62:95:ee:eb:59:5f:c6:e1:
+                    05:80:12:c4:5e:94:3f:bc:5b:48:38:f4:53:f7:24:
+                    e6:fb:91:e9:15:c4:cf:f4:53:0d:f4:4a:fc:9f:54:
+                    de:7d:be:a0:6b:6f:87:c0:d0:50:1f:28:30:03:40:
+                    da:08:73:51:6c:7f:ff:3a:3c:a7:37:06:8e:bd:4b:
+                    11:04:eb:7d:24:de:e6:f9:fc:31:71:fb:94:d5:60:
+                    f3:2e:4a:af:42:d2:cb:ea:c4:6a:1a:b2:cc:53:dd:
+                    15:4b:8b:1f:c8:19:61:1f:cd:9d:a8:3e:63:2b:84:
+                    35:69:65:84:c8:19:c5:46:22:f8:53:95:be:e3:80:
+                    4a:10:c6:2a:ec:ba:97:20:11:c7:39:99:10:04:a0:
+                    f0:61:7a:95:25:8c:4e:52:75:e2:b6:ed:08:ca:14:
+                    fc:ce:22:6a:b3:4e:cf:46:03:97:97:03:7e:c0:b1:
+                    de:7b:af:45:33:cf:ba:3e:71:b7:de:f4:25:25:c2:
+                    0d:35:89:9d:9d:fb:0e:11:79:89:1e:37:c5:af:8e:
+                    72:69
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+            X509v3 Subject Key Identifier: 
+                4A:DD:06:16:1B:BC:F6:68:B5:76:F5:81:B6:BB:62:1A:BA:5A:81:2F
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://gtglobal-ocsp.geotrust.com
+
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.11129.2.5.1
+
+    Signature Algorithm: sha1WithRSAEncryption
+         36:d7:06:80:11:27:ad:2a:14:9b:38:77:b3:23:a0:75:58:bb:
+         b1:7e:83:42:ba:72:da:1e:d8:8e:36:06:97:e0:f0:95:3b:37:
+         fd:1b:42:58:fe:22:c8:6b:bd:38:5e:d1:3b:25:6e:12:eb:5e:
+         67:76:46:40:90:da:14:c8:78:0d:ed:95:66:da:8e:86:6f:80:
+         a1:ba:56:32:95:86:dc:dc:6a:ca:04:8c:5b:7f:f6:bf:cc:6f:
+         85:03:58:c3:68:51:13:cd:fd:c8:f7:79:3d:99:35:f0:56:a3:
+         bd:e0:59:ed:4f:44:09:a3:9e:38:7a:f6:46:d1:1d:12:9d:4f:
+         be:d0:40:fc:55:fe:06:5e:3c:da:1c:56:bd:96:51:7b:6f:57:
+         2a:db:a2:aa:96:dc:8c:74:c2:95:be:f0:6e:95:13:ff:17:f0:
+         3c:ac:b2:10:8d:cc:73:fb:e8:8f:02:c6:f0:fb:33:b3:95:3b:
+         e3:c2:cb:68:58:73:db:a8:24:62:3b:06:35:9d:0d:a9:33:bd:
+         78:03:90:2e:4c:78:5d:50:3a:81:d4:ee:a0:c8:70:38:dc:b2:
+         f9:67:fa:87:40:5d:61:c0:51:8f:6b:83:6b:cd:05:3a:ca:e1:
+         a7:05:78:fc:ca:da:94:d0:2c:08:3d:7e:16:79:c8:a0:50:20:
+         24:54:33:71
+-----BEGIN CERTIFICATE-----
+MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
+EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
+bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
+VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
+h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
+ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
+EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
+DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
+qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
+VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
+K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
+KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
+ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
+BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
+/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
+zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
+HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
+WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
+yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert10[] = {
+  0x30, 0x82, 0x04, 0x04, 0x30, 0x82, 0x02, 0xec, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x02, 0x3a, 0x69, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+  0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30,
+  0x34, 0x30, 0x35, 0x31, 0x35, 0x31, 0x35, 0x35, 0x35, 0x5a, 0x17, 0x0d,
+  0x31, 0x35, 0x30, 0x34, 0x30, 0x34, 0x31, 0x35, 0x31, 0x35, 0x35, 0x35,
+  0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+  0x63, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+  0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+  0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0x9c, 0x2a, 0x04, 0x77, 0x5c, 0xd8, 0x50, 0x91, 0x3a, 0x06, 0xa3,
+  0x82, 0xe0, 0xd8, 0x50, 0x48, 0xbc, 0x89, 0x3f, 0xf1, 0x19, 0x70, 0x1a,
+  0x88, 0x46, 0x7e, 0xe0, 0x8f, 0xc5, 0xf1, 0x89, 0xce, 0x21, 0xee, 0x5a,
+  0xfe, 0x61, 0x0d, 0xb7, 0x32, 0x44, 0x89, 0xa0, 0x74, 0x0b, 0x53, 0x4f,
+  0x55, 0xa4, 0xce, 0x82, 0x62, 0x95, 0xee, 0xeb, 0x59, 0x5f, 0xc6, 0xe1,
+  0x05, 0x80, 0x12, 0xc4, 0x5e, 0x94, 0x3f, 0xbc, 0x5b, 0x48, 0x38, 0xf4,
+  0x53, 0xf7, 0x24, 0xe6, 0xfb, 0x91, 0xe9, 0x15, 0xc4, 0xcf, 0xf4, 0x53,
+  0x0d, 0xf4, 0x4a, 0xfc, 0x9f, 0x54, 0xde, 0x7d, 0xbe, 0xa0, 0x6b, 0x6f,
+  0x87, 0xc0, 0xd0, 0x50, 0x1f, 0x28, 0x30, 0x03, 0x40, 0xda, 0x08, 0x73,
+  0x51, 0x6c, 0x7f, 0xff, 0x3a, 0x3c, 0xa7, 0x37, 0x06, 0x8e, 0xbd, 0x4b,
+  0x11, 0x04, 0xeb, 0x7d, 0x24, 0xde, 0xe6, 0xf9, 0xfc, 0x31, 0x71, 0xfb,
+  0x94, 0xd5, 0x60, 0xf3, 0x2e, 0x4a, 0xaf, 0x42, 0xd2, 0xcb, 0xea, 0xc4,
+  0x6a, 0x1a, 0xb2, 0xcc, 0x53, 0xdd, 0x15, 0x4b, 0x8b, 0x1f, 0xc8, 0x19,
+  0x61, 0x1f, 0xcd, 0x9d, 0xa8, 0x3e, 0x63, 0x2b, 0x84, 0x35, 0x69, 0x65,
+  0x84, 0xc8, 0x19, 0xc5, 0x46, 0x22, 0xf8, 0x53, 0x95, 0xbe, 0xe3, 0x80,
+  0x4a, 0x10, 0xc6, 0x2a, 0xec, 0xba, 0x97, 0x20, 0x11, 0xc7, 0x39, 0x99,
+  0x10, 0x04, 0xa0, 0xf0, 0x61, 0x7a, 0x95, 0x25, 0x8c, 0x4e, 0x52, 0x75,
+  0xe2, 0xb6, 0xed, 0x08, 0xca, 0x14, 0xfc, 0xce, 0x22, 0x6a, 0xb3, 0x4e,
+  0xcf, 0x46, 0x03, 0x97, 0x97, 0x03, 0x7e, 0xc0, 0xb1, 0xde, 0x7b, 0xaf,
+  0x45, 0x33, 0xcf, 0xba, 0x3e, 0x71, 0xb7, 0xde, 0xf4, 0x25, 0x25, 0xc2,
+  0x0d, 0x35, 0x89, 0x9d, 0x9d, 0xfb, 0x0e, 0x11, 0x79, 0x89, 0x1e, 0x37,
+  0xc5, 0xaf, 0x8e, 0x72, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+  0xfb, 0x30, 0x81, 0xf8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb,
+  0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc,
+  0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x4a, 0xdd, 0x06, 0x16, 0x1b, 0xbc, 0xf6, 0x68, 0xb5, 0x76, 0xf5, 0x81,
+  0xb6, 0xbb, 0x62, 0x1a, 0xba, 0x5a, 0x81, 0x2f, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+  0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+  0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x74, 0x67,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2d, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67,
+  0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+  0x17, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x10, 0x30, 0x0e, 0x30, 0x0c,
+  0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x05, 0x01,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x36, 0xd7, 0x06, 0x80,
+  0x11, 0x27, 0xad, 0x2a, 0x14, 0x9b, 0x38, 0x77, 0xb3, 0x23, 0xa0, 0x75,
+  0x58, 0xbb, 0xb1, 0x7e, 0x83, 0x42, 0xba, 0x72, 0xda, 0x1e, 0xd8, 0x8e,
+  0x36, 0x06, 0x97, 0xe0, 0xf0, 0x95, 0x3b, 0x37, 0xfd, 0x1b, 0x42, 0x58,
+  0xfe, 0x22, 0xc8, 0x6b, 0xbd, 0x38, 0x5e, 0xd1, 0x3b, 0x25, 0x6e, 0x12,
+  0xeb, 0x5e, 0x67, 0x76, 0x46, 0x40, 0x90, 0xda, 0x14, 0xc8, 0x78, 0x0d,
+  0xed, 0x95, 0x66, 0xda, 0x8e, 0x86, 0x6f, 0x80, 0xa1, 0xba, 0x56, 0x32,
+  0x95, 0x86, 0xdc, 0xdc, 0x6a, 0xca, 0x04, 0x8c, 0x5b, 0x7f, 0xf6, 0xbf,
+  0xcc, 0x6f, 0x85, 0x03, 0x58, 0xc3, 0x68, 0x51, 0x13, 0xcd, 0xfd, 0xc8,
+  0xf7, 0x79, 0x3d, 0x99, 0x35, 0xf0, 0x56, 0xa3, 0xbd, 0xe0, 0x59, 0xed,
+  0x4f, 0x44, 0x09, 0xa3, 0x9e, 0x38, 0x7a, 0xf6, 0x46, 0xd1, 0x1d, 0x12,
+  0x9d, 0x4f, 0xbe, 0xd0, 0x40, 0xfc, 0x55, 0xfe, 0x06, 0x5e, 0x3c, 0xda,
+  0x1c, 0x56, 0xbd, 0x96, 0x51, 0x7b, 0x6f, 0x57, 0x2a, 0xdb, 0xa2, 0xaa,
+  0x96, 0xdc, 0x8c, 0x74, 0xc2, 0x95, 0xbe, 0xf0, 0x6e, 0x95, 0x13, 0xff,
+  0x17, 0xf0, 0x3c, 0xac, 0xb2, 0x10, 0x8d, 0xcc, 0x73, 0xfb, 0xe8, 0x8f,
+  0x02, 0xc6, 0xf0, 0xfb, 0x33, 0xb3, 0x95, 0x3b, 0xe3, 0xc2, 0xcb, 0x68,
+  0x58, 0x73, 0xdb, 0xa8, 0x24, 0x62, 0x3b, 0x06, 0x35, 0x9d, 0x0d, 0xa9,
+  0x33, 0xbd, 0x78, 0x03, 0x90, 0x2e, 0x4c, 0x78, 0x5d, 0x50, 0x3a, 0x81,
+  0xd4, 0xee, 0xa0, 0xc8, 0x70, 0x38, 0xdc, 0xb2, 0xf9, 0x67, 0xfa, 0x87,
+  0x40, 0x5d, 0x61, 0xc0, 0x51, 0x8f, 0x6b, 0x83, 0x6b, 0xcd, 0x05, 0x3a,
+  0xca, 0xe1, 0xa7, 0x05, 0x78, 0xfc, 0xca, 0xda, 0x94, 0xd0, 0x2c, 0x08,
+  0x3d, 0x7e, 0x16, 0x79, 0xc8, 0xa0, 0x50, 0x20, 0x24, 0x54, 0x33, 0x71,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120033005 (0x7278eed)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Apr 18 16:36:18 2012 GMT
+            Not After : Aug 13 16:35:17 2018 GMT
+        Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+                    d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+                    64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+                    62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+                    52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+                    73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+                    50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+                    a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+                    70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+                    d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+                    5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+                    98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+                    ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+                    39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+                    c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+                    ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+                    78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+                    1a:39
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:3
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://cybertrust.omniroot.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         93:1d:fe:8b:ae:46:ec:cb:a9:0f:ab:e5:ef:ca:b2:68:16:68:
+         d8:8f:fa:13:a9:af:b3:cb:2d:e7:4b:6e:8e:69:2a:c2:2b:10:
+         0a:8d:f6:ae:73:b6:b9:fb:14:fd:5f:6d:b8:50:b6:c4:8a:d6:
+         40:7e:d7:c3:cb:73:dc:c9:5d:5b:af:b0:41:b5:37:eb:ea:dc:
+         20:91:c4:34:6a:f4:a1:f3:96:9d:37:86:97:e1:71:a4:dd:7d:
+         fa:44:84:94:ae:d7:09:04:22:76:0f:64:51:35:a9:24:0f:f9:
+         0b:db:32:da:c2:fe:c1:b9:2a:5c:7a:27:13:ca:b1:48:3a:71:
+         d0:43
+-----BEGIN CERTIFICATE-----
+MIIEFTCCA36gAwIBAgIEByeO7TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEyMDQxODE2MzYxOFoXDTE4MDgxMzE2MzUxN1owWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAUcw
+ggFDMBIGA1UdEwEB/wQIMAYBAf8CAQMwSgYDVR0gBEMwQTA/BgRVHSAAMDcwNQYI
+KwYBBQUHAgEWKWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5MA4GA1UdDwEB/wQEAwIBBjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYT
+AlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJl
+clRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3Qg
+R2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVi
+bGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwDQYJKoZIhvcN
+AQEFBQADgYEAkx3+i65G7MupD6vl78qyaBZo2I/6E6mvs8st50tujmkqwisQCo32
+rnO2ufsU/V9tuFC2xIrWQH7Xw8tz3MldW6+wQbU36+rcIJHENGr0ofOWnTeGl+Fx
+pN19+kSElK7XCQQidg9kUTWpJA/5C9sy2sL+wbkqXHonE8qxSDpx0EM=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert11[] = {
+  0x30, 0x82, 0x04, 0x15, 0x30, 0x82, 0x03, 0x7e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x8e, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x32, 0x30, 0x34, 0x31, 0x38, 0x31, 0x36, 0x33, 0x36, 0x31,
+  0x38, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x31, 0x36,
+  0x33, 0x35, 0x31, 0x37, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69,
+  0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79,
+  0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f,
+  0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f,
+  0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04,
+  0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79,
+  0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e,
+  0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d,
+  0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12,
+  0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88,
+  0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7,
+  0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5,
+  0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab,
+  0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5,
+  0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f,
+  0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77,
+  0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0,
+  0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd,
+  0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0,
+  0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4,
+  0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9,
+  0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c,
+  0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c,
+  0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b,
+  0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76,
+  0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27,
+  0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x47, 0x30,
+  0x82, 0x01, 0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30,
+  0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x37, 0x30, 0x35, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x29, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+  0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+  0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03,
+  0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77,
+  0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+  0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23,
+  0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45,
+  0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82,
+  0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e,
+  0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52,
+  0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x93, 0x1d, 0xfe,
+  0x8b, 0xae, 0x46, 0xec, 0xcb, 0xa9, 0x0f, 0xab, 0xe5, 0xef, 0xca, 0xb2,
+  0x68, 0x16, 0x68, 0xd8, 0x8f, 0xfa, 0x13, 0xa9, 0xaf, 0xb3, 0xcb, 0x2d,
+  0xe7, 0x4b, 0x6e, 0x8e, 0x69, 0x2a, 0xc2, 0x2b, 0x10, 0x0a, 0x8d, 0xf6,
+  0xae, 0x73, 0xb6, 0xb9, 0xfb, 0x14, 0xfd, 0x5f, 0x6d, 0xb8, 0x50, 0xb6,
+  0xc4, 0x8a, 0xd6, 0x40, 0x7e, 0xd7, 0xc3, 0xcb, 0x73, 0xdc, 0xc9, 0x5d,
+  0x5b, 0xaf, 0xb0, 0x41, 0xb5, 0x37, 0xeb, 0xea, 0xdc, 0x20, 0x91, 0xc4,
+  0x34, 0x6a, 0xf4, 0xa1, 0xf3, 0x96, 0x9d, 0x37, 0x86, 0x97, 0xe1, 0x71,
+  0xa4, 0xdd, 0x7d, 0xfa, 0x44, 0x84, 0x94, 0xae, 0xd7, 0x09, 0x04, 0x22,
+  0x76, 0x0f, 0x64, 0x51, 0x35, 0xa9, 0x24, 0x0f, 0xf9, 0x0b, 0xdb, 0x32,
+  0xda, 0xc2, 0xfe, 0xc1, 0xb9, 0x2a, 0x5c, 0x7a, 0x27, 0x13, 0xca, 0xb1,
+  0x48, 0x3a, 0x71, 0xd0, 0x43,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120010508 (0x727370c)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Validity
+            Not Before: Sep  8 17:35:16 2010 GMT
+            Not After : Sep  8 17:34:08 2020 GMT
+        Subject: O=Cybertrust Inc, CN=Cybertrust Public SureServer SV CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a3:ba:99:8d:b7:e1:cd:73:88:f9:b9:dd:de:f4:
+                    05:f3:25:f5:3f:c5:52:1e:51:5a:3f:9a:ff:4d:84:
+                    b7:50:7f:f1:10:8a:5d:7f:64:55:1c:3b:a3:f3:ff:
+                    97:7f:1c:4b:ed:6f:7f:e9:54:ec:97:2a:42:03:67:
+                    7f:b9:c8:6c:a2:97:f8:40:93:24:c3:25:5e:a5:66:
+                    8b:86:bd:d7:b9:26:22:6e:d2:66:83:b3:78:c1:7c:
+                    58:76:11:eb:16:55:47:32:f0:b9:34:10:bd:8f:26:
+                    a2:25:68:c1:14:2b:a2:73:d6:66:3d:44:87:5c:13:
+                    7f:58:91:62:3d:57:7f:6c:ae:42:e8:12:7e:bd:78:
+                    f1:f1:ac:5c:35:60:68:45:bc:53:73:87:11:1d:c5:
+                    2e:fa:60:35:da:91:f9:da:f2:55:6c:bf:ca:a2:57:
+                    5c:c8:64:bc:a9:5b:15:a0:fc:1c:f3:44:2e:bd:06:
+                    f2:68:d8:40:2d:bb:b3:61:25:92:93:25:1c:77:46:
+                    90:bf:d0:af:b7:83:a0:3c:87:5e:a5:91:a8:ff:c1:
+                    31:1b:b6:4b:ac:12:34:08:d5:db:ec:89:87:63:06:
+                    a7:53:f8:d5:f5:e6:66:ac:5e:84:65:46:c9:f4:3a:
+                    25:0f:6c:cc:0f:66:b8:9a:55:a1:46:6c:fc:91:23:
+                    5f:bd
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.50
+                  CPS: http://cybertrust.omniroot.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+            X509v3 Subject Key Identifier: 
+                04:98:60:DF:80:1B:96:49:5D:65:56:2D:A5:2C:09:24:0A:EC:DC:B9
+    Signature Algorithm: sha1WithRSAEncryption
+         5f:df:8b:cf:29:79:78:2b:f3:7c:f4:82:5f:79:e0:e1:b3:28:
+         bd:08:75:41:ce:8c:88:d7:0e:55:b9:02:b5:05:79:3e:bb:52:
+         31:b3:4b:1e:b1:fe:d3:a2:21:43:d2:91:d3:16:fa:6b:79:e4:
+         8e:4d:19:ec:4c:86:68:34:52:b7:6f:c2:bd:9c:78:be:f0:6f:
+         3f:3d:9e:9f:49:74:c4:7c:97:19:45:57:ac:6f:fa:5a:3e:3f:
+         d3:d6:e3:2b:dc:8a:f8:c8:0a:0d:6b:8c:3f:94:78:37:98:88:
+         61:91:df:59:14:0f:09:c5:63:54:fb:f4:f6:af:97:ec:fc:63:
+         64:43:a6:bc:cc:e4:e3:1f:df:73:b0:6e:f7:b5:c8:29:9b:ae:
+         25:52:b8:b4:72:e1:de:93:48:f1:28:9f:7e:66:3f:3f:8b:55:
+         0f:f8:16:07:71:05:d7:65:9c:d7:1b:3c:34:e6:44:16:3a:bd:
+         d8:60:93:83:83:0c:88:96:65:33:40:df:6a:ac:ff:fe:94:51:
+         61:bb:89:3f:f7:ac:c4:e4:b3:47:e2:fd:a2:6a:32:83:e2:7e:
+         6f:f0:12:8e:a3:66:76:40:97:fb:11:e1:f7:73:1f:da:8b:1c:
+         31:42:8b:9f:11:c5:49:a5:60:ed:48:2b:05:84:15:ab:2f:8a:
+         2c:51:72:c0
+-----BEGIN CERTIFICATE-----
+MIIEGzCCAwOgAwIBAgIEByc3DDANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEwMDkwODE3MzUxNloX
+DTIwMDkwODE3MzQwOFowRjEXMBUGA1UEChMOQ3liZXJ0cnVzdCBJbmMxKzApBgNV
+BAMTIkN5YmVydHJ1c3QgUHVibGljIFN1cmVTZXJ2ZXIgU1YgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjupmNt+HNc4j5ud3e9AXzJfU/xVIeUVo/
+mv9NhLdQf/EQil1/ZFUcO6Pz/5d/HEvtb3/pVOyXKkIDZ3+5yGyil/hAkyTDJV6l
+ZouGvde5JiJu0maDs3jBfFh2EesWVUcy8Lk0EL2PJqIlaMEUK6Jz1mY9RIdcE39Y
+kWI9V39srkLoEn69ePHxrFw1YGhFvFNzhxEdxS76YDXakfna8lVsv8qiV1zIZLyp
+WxWg/BzzRC69BvJo2EAtu7NhJZKTJRx3RpC/0K+3g6A8h16lkaj/wTEbtkusEjQI
+1dvsiYdjBqdT+NX15masXoRlRsn0OiUPbMwPZriaVaFGbPyRI1+9AgMBAAGjgfww
+gfkwEgYDVR0TAQH/BAgwBgEB/wIBADBPBgNVHSAESDBGMEQGCSsGAQQBsT4BMjA3
+MDUGCCsGAQUFBwIBFilodHRwOi8vY3liZXJ0cnVzdC5vbW5pcm9vdC5jb20vcmVw
+b3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAU5Z1ZMIJHWMys+ghU
+NoZ7OrUETfAwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NkcDEucHVibGljLXRy
+dXN0LmNvbS9DUkwvT21uaXJvb3QyMDI1LmNybDAdBgNVHQ4EFgQUBJhg34Ablkld
+ZVYtpSwJJArs3LkwDQYJKoZIhvcNAQEFBQADggEBAF/fi88peXgr83z0gl954OGz
+KL0IdUHOjIjXDlW5ArUFeT67UjGzSx6x/tOiIUPSkdMW+mt55I5NGexMhmg0Urdv
+wr2ceL7wbz89np9JdMR8lxlFV6xv+lo+P9PW4yvcivjICg1rjD+UeDeYiGGR31kU
+DwnFY1T79Pavl+z8Y2RDprzM5OMf33Owbve1yCmbriVSuLRy4d6TSPEon35mPz+L
+VQ/4FgdxBddlnNcbPDTmRBY6vdhgk4ODDIiWZTNA32qs//6UUWG7iT/3rMTks0fi
+/aJqMoPifm/wEo6jZnZAl/sR4fdzH9qLHDFCi58RxUmlYO1IKwWEFasviixRcsA=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert12[] = {
+  0x30, 0x82, 0x04, 0x1b, 0x30, 0x82, 0x03, 0x03, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x37, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+  0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+  0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30,
+  0x30, 0x39, 0x30, 0x38, 0x31, 0x37, 0x33, 0x35, 0x31, 0x36, 0x5a, 0x17,
+  0x0d, 0x32, 0x30, 0x30, 0x39, 0x30, 0x38, 0x31, 0x37, 0x33, 0x34, 0x30,
+  0x38, 0x5a, 0x30, 0x46, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0e, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x22, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x53, 0x75,
+  0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x53, 0x56, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3,
+  0xba, 0x99, 0x8d, 0xb7, 0xe1, 0xcd, 0x73, 0x88, 0xf9, 0xb9, 0xdd, 0xde,
+  0xf4, 0x05, 0xf3, 0x25, 0xf5, 0x3f, 0xc5, 0x52, 0x1e, 0x51, 0x5a, 0x3f,
+  0x9a, 0xff, 0x4d, 0x84, 0xb7, 0x50, 0x7f, 0xf1, 0x10, 0x8a, 0x5d, 0x7f,
+  0x64, 0x55, 0x1c, 0x3b, 0xa3, 0xf3, 0xff, 0x97, 0x7f, 0x1c, 0x4b, 0xed,
+  0x6f, 0x7f, 0xe9, 0x54, 0xec, 0x97, 0x2a, 0x42, 0x03, 0x67, 0x7f, 0xb9,
+  0xc8, 0x6c, 0xa2, 0x97, 0xf8, 0x40, 0x93, 0x24, 0xc3, 0x25, 0x5e, 0xa5,
+  0x66, 0x8b, 0x86, 0xbd, 0xd7, 0xb9, 0x26, 0x22, 0x6e, 0xd2, 0x66, 0x83,
+  0xb3, 0x78, 0xc1, 0x7c, 0x58, 0x76, 0x11, 0xeb, 0x16, 0x55, 0x47, 0x32,
+  0xf0, 0xb9, 0x34, 0x10, 0xbd, 0x8f, 0x26, 0xa2, 0x25, 0x68, 0xc1, 0x14,
+  0x2b, 0xa2, 0x73, 0xd6, 0x66, 0x3d, 0x44, 0x87, 0x5c, 0x13, 0x7f, 0x58,
+  0x91, 0x62, 0x3d, 0x57, 0x7f, 0x6c, 0xae, 0x42, 0xe8, 0x12, 0x7e, 0xbd,
+  0x78, 0xf1, 0xf1, 0xac, 0x5c, 0x35, 0x60, 0x68, 0x45, 0xbc, 0x53, 0x73,
+  0x87, 0x11, 0x1d, 0xc5, 0x2e, 0xfa, 0x60, 0x35, 0xda, 0x91, 0xf9, 0xda,
+  0xf2, 0x55, 0x6c, 0xbf, 0xca, 0xa2, 0x57, 0x5c, 0xc8, 0x64, 0xbc, 0xa9,
+  0x5b, 0x15, 0xa0, 0xfc, 0x1c, 0xf3, 0x44, 0x2e, 0xbd, 0x06, 0xf2, 0x68,
+  0xd8, 0x40, 0x2d, 0xbb, 0xb3, 0x61, 0x25, 0x92, 0x93, 0x25, 0x1c, 0x77,
+  0x46, 0x90, 0xbf, 0xd0, 0xaf, 0xb7, 0x83, 0xa0, 0x3c, 0x87, 0x5e, 0xa5,
+  0x91, 0xa8, 0xff, 0xc1, 0x31, 0x1b, 0xb6, 0x4b, 0xac, 0x12, 0x34, 0x08,
+  0xd5, 0xdb, 0xec, 0x89, 0x87, 0x63, 0x06, 0xa7, 0x53, 0xf8, 0xd5, 0xf5,
+  0xe6, 0x66, 0xac, 0x5e, 0x84, 0x65, 0x46, 0xc9, 0xf4, 0x3a, 0x25, 0x0f,
+  0x6c, 0xcc, 0x0f, 0x66, 0xb8, 0x9a, 0x55, 0xa1, 0x46, 0x6c, 0xfc, 0x91,
+  0x23, 0x5f, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfc, 0x30,
+  0x81, 0xf9, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x4f,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x48, 0x30, 0x46, 0x30, 0x44, 0x06,
+  0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x32, 0x30, 0x37,
+  0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+  0x16, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69,
+  0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70,
+  0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54,
+  0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0,
+  0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64,
+  0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f,
+  0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x04, 0x98, 0x60, 0xdf, 0x80, 0x1b, 0x96, 0x49, 0x5d,
+  0x65, 0x56, 0x2d, 0xa5, 0x2c, 0x09, 0x24, 0x0a, 0xec, 0xdc, 0xb9, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5f, 0xdf, 0x8b, 0xcf, 0x29,
+  0x79, 0x78, 0x2b, 0xf3, 0x7c, 0xf4, 0x82, 0x5f, 0x79, 0xe0, 0xe1, 0xb3,
+  0x28, 0xbd, 0x08, 0x75, 0x41, 0xce, 0x8c, 0x88, 0xd7, 0x0e, 0x55, 0xb9,
+  0x02, 0xb5, 0x05, 0x79, 0x3e, 0xbb, 0x52, 0x31, 0xb3, 0x4b, 0x1e, 0xb1,
+  0xfe, 0xd3, 0xa2, 0x21, 0x43, 0xd2, 0x91, 0xd3, 0x16, 0xfa, 0x6b, 0x79,
+  0xe4, 0x8e, 0x4d, 0x19, 0xec, 0x4c, 0x86, 0x68, 0x34, 0x52, 0xb7, 0x6f,
+  0xc2, 0xbd, 0x9c, 0x78, 0xbe, 0xf0, 0x6f, 0x3f, 0x3d, 0x9e, 0x9f, 0x49,
+  0x74, 0xc4, 0x7c, 0x97, 0x19, 0x45, 0x57, 0xac, 0x6f, 0xfa, 0x5a, 0x3e,
+  0x3f, 0xd3, 0xd6, 0xe3, 0x2b, 0xdc, 0x8a, 0xf8, 0xc8, 0x0a, 0x0d, 0x6b,
+  0x8c, 0x3f, 0x94, 0x78, 0x37, 0x98, 0x88, 0x61, 0x91, 0xdf, 0x59, 0x14,
+  0x0f, 0x09, 0xc5, 0x63, 0x54, 0xfb, 0xf4, 0xf6, 0xaf, 0x97, 0xec, 0xfc,
+  0x63, 0x64, 0x43, 0xa6, 0xbc, 0xcc, 0xe4, 0xe3, 0x1f, 0xdf, 0x73, 0xb0,
+  0x6e, 0xf7, 0xb5, 0xc8, 0x29, 0x9b, 0xae, 0x25, 0x52, 0xb8, 0xb4, 0x72,
+  0xe1, 0xde, 0x93, 0x48, 0xf1, 0x28, 0x9f, 0x7e, 0x66, 0x3f, 0x3f, 0x8b,
+  0x55, 0x0f, 0xf8, 0x16, 0x07, 0x71, 0x05, 0xd7, 0x65, 0x9c, 0xd7, 0x1b,
+  0x3c, 0x34, 0xe6, 0x44, 0x16, 0x3a, 0xbd, 0xd8, 0x60, 0x93, 0x83, 0x83,
+  0x0c, 0x88, 0x96, 0x65, 0x33, 0x40, 0xdf, 0x6a, 0xac, 0xff, 0xfe, 0x94,
+  0x51, 0x61, 0xbb, 0x89, 0x3f, 0xf7, 0xac, 0xc4, 0xe4, 0xb3, 0x47, 0xe2,
+  0xfd, 0xa2, 0x6a, 0x32, 0x83, 0xe2, 0x7e, 0x6f, 0xf0, 0x12, 0x8e, 0xa3,
+  0x66, 0x76, 0x40, 0x97, 0xfb, 0x11, 0xe1, 0xf7, 0x73, 0x1f, 0xda, 0x8b,
+  0x1c, 0x31, 0x42, 0x8b, 0x9f, 0x11, 0xc5, 0x49, 0xa5, 0x60, 0xed, 0x48,
+  0x2b, 0x05, 0x84, 0x15, 0xab, 0x2f, 0x8a, 0x2c, 0x51, 0x72, 0xc0,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 7 (0x7)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=America Online Inc., CN=America Online Root Certification Authority 1
+        Validity
+            Not Before: Jun  4 17:26:39 2004 GMT
+            Not After : Jun  4 17:26:39 2029 GMT
+        Subject: C=US, ST=Virginia, L=Dulles, O=America Online Inc., CN=AOL Member CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:f8:63:d8:f1:cc:d6:11:6f:05:e1:d6:9f:43:27:
+                    27:25:fa:af:67:87:74:ac:dc:d6:fc:c2:9a:bd:33:
+                    72:4e:65:b0:5a:cd:eb:f8:a9:69:48:39:6e:68:2e:
+                    71:16:6e:9e:59:7c:c2:7c:cd:ed:6e:43:d8:09:42:
+                    4e:0d:9a:7d:ee:b5:5a:23:81:d2:a4:5b:a9:51:54:
+                    1c:df:f6:84:df:19:c3:3e:94:2d:8d:ba:10:f8:e8:
+                    43:08:0f:32:35:6c:35:31:f8:d6:d3:fc:09:31:d6:
+                    a9:a1:7a:20:06:59:0c:e0:2b:8d:84:c3:37:a0:08:
+                    1e:f1:35:73:10:dd:4f:fd:0c:72:93:26:6e:af:c5:
+                    1c:39:e3:ca:f3:95:6f:30:c2:85:3d:4d:84:20:c8:
+                    3e:3d:d0:40:d6:fe:06:4a:18:73:0b:6e:57:67:db:
+                    83:c1:13:66:97:d3:bd:59:bc:7e:fa:2f:36:45:14:
+                    cd:bc:bf:ab:68:77:bf:48:eb:11:89:4e:6a:84:f3:
+                    5d:1c:e5:6b:6a:00:e6:6b:8d:48:a4:09:b9:21:dc:
+                    2d:66:29:f4:56:9e:f0:05:68:ff:cc:c1:c9:88:bc:
+                    d2:2c:0b:af:1d:74:1a:86:68:a4:6d:14:74:ec:24:
+                    80:f8:95:b9:f3:2e:3c:3d:20:6f:09:02:38:ea:3a:
+                    38:05
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                61:A6:99:6D:24:9F:0E:11:88:E6:39:E0:FE:74:D1:05:69:52:A9:43
+            X509v3 Authority Key Identifier: 
+                keyid:00:AD:D9:A3:F6:79:F6:6E:74:A9:7F:33:3D:81:17:D7:4C:CF:33:DE
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://pki-info.aol.com/AOL/index.html
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.aol.com/AOL/MasterCRL.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         0c:9d:bc:cc:1d:d4:2e:91:1b:af:a0:3a:eb:cd:5e:fc:25:2a:
+         29:8e:b3:20:e0:17:37:fa:fc:bb:c5:b5:14:bb:1c:66:0e:6f:
+         58:5f:c6:71:d0:13:89:c7:ad:23:0b:ed:4c:b8:58:c1:e3:c2:
+         a4:24:4c:65:76:0d:b3:86:64:4f:28:ba:cf:96:f8:65:9a:0e:
+         82:26:f5:82:85:4e:35:20:b3:45:cc:60:ee:0f:4e:20:94:3a:
+         2b:2f:cb:23:10:9b:46:1b:7e:c3:56:75:49:24:a4:b8:4d:9f:
+         1c:68:d4:e6:f2:2f:af:8e:ed:2b:b7:e5:96:6b:1c:3d:8d:bf:
+         20:d3:6f:2d:54:2f:9c:79:35:fd:da:06:de:68:20:20:4b:af:
+         5d:ab:5e:66:c3:14:64:7b:f7:02:e6:27:96:ad:18:1e:ab:f3:
+         82:60:fc:4c:5f:b6:0a:52:7b:9e:9c:3b:2e:ce:3c:42:5f:36:
+         6d:6b:fe:a1:76:8a:22:21:fd:5b:e8:bd:7f:9f:ce:51:74:48:
+         6c:ac:b5:d1:a2:6a:fa:07:44:de:d0:db:a9:8d:18:1f:f1:b9:
+         c5:e8:2a:eb:ba:3d:3b:18:8c:c0:0c:30:b3:c9:21:1c:33:4c:
+         3a:49:53:d4:a8:ba:ba:38:23:3d:3a:65:82:5e:79:71:15:f8:
+         25:2b:7d:19
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTA0MDYwNDE3
+MjYzOVoXDTI5MDYwNDE3MjYzOVowZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZp
+cmdpbmlhMQ8wDQYDVQQHEwZEdWxsZXMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5l
+IEluYy4xFjAUBgNVBAMTDUFPTCBNZW1iZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQD4Y9jxzNYRbwXh1p9DJycl+q9nh3Ss3Nb8wpq9M3JOZbBa
+zev4qWlIOW5oLnEWbp5ZfMJ8ze1uQ9gJQk4Nmn3utVojgdKkW6lRVBzf9oTfGcM+
+lC2NuhD46EMIDzI1bDUx+NbT/Akx1qmheiAGWQzgK42EwzegCB7xNXMQ3U/9DHKT
+Jm6vxRw548rzlW8wwoU9TYQgyD490EDW/gZKGHMLbldn24PBE2aX071ZvH76LzZF
+FM28v6tod79I6xGJTmqE810c5WtqAOZrjUikCbkh3C1mKfRWnvAFaP/MwcmIvNIs
+C68ddBqGaKRtFHTsJID4lbnzLjw9IG8JAjjqOjgFAgMBAAGjgeUwgeIwDgYDVR0P
+AQH/BAQDAgGGMB0GA1UdDgQWBBRhppltJJ8OEYjmOeD+dNEFaVKpQzAfBgNVHSME
+GDAWgBQArdmj9nn2bnSpfzM9gRfXTM8z3jAPBgNVHRMBAf8EBTADAQH/MEgGA1Ud
+IARBMD8wPQYEVR0gADA1MDMGCCsGAQUFBwIBFidodHRwczovL3BraS1pbmZvLmFv
+bC5jb20vQU9ML2luZGV4Lmh0bWwwNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2Ny
+bC5hb2wuY29tL0FPTC9NYXN0ZXJDUkwuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAM
+nbzMHdQukRuvoDrrzV78JSopjrMg4Bc3+vy7xbUUuxxmDm9YX8Zx0BOJx60jC+1M
+uFjB48KkJExldg2zhmRPKLrPlvhlmg6CJvWChU41ILNFzGDuD04glDorL8sjEJtG
+G37DVnVJJKS4TZ8caNTm8i+vju0rt+WWaxw9jb8g028tVC+ceTX92gbeaCAgS69d
+q15mwxRke/cC5ieWrRgeq/OCYPxMX7YKUnuenDsuzjxCXzZta/6hdooiIf1b6L1/
+n85RdEhsrLXRomr6B0Te0NupjRgf8bnF6Crruj07GIzADDCzySEcM0w6SVPUqLq6
+OCM9OmWCXnlxFfglK30Z
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert13[] = {
+  0x30, 0x82, 0x04, 0x2b, 0x30, 0x82, 0x03, 0x13, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x63, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1c,
+  0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x13, 0x41, 0x6d, 0x65,
+  0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x2d, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f,
+  0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x31,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34, 0x30, 0x36, 0x30, 0x34, 0x31, 0x37,
+  0x32, 0x36, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x39, 0x30, 0x36, 0x30,
+  0x34, 0x31, 0x37, 0x32, 0x36, 0x33, 0x39, 0x5a, 0x30, 0x67, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x08, 0x56, 0x69,
+  0x72, 0x67, 0x69, 0x6e, 0x69, 0x61, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03,
+  0x55, 0x04, 0x07, 0x13, 0x06, 0x44, 0x75, 0x6c, 0x6c, 0x65, 0x73, 0x31,
+  0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x13, 0x41, 0x6d,
+  0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+  0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0d, 0x41, 0x4f, 0x4c, 0x20, 0x4d, 0x65, 0x6d, 0x62,
+  0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xf8, 0x63, 0xd8, 0xf1, 0xcc, 0xd6, 0x11, 0x6f, 0x05, 0xe1,
+  0xd6, 0x9f, 0x43, 0x27, 0x27, 0x25, 0xfa, 0xaf, 0x67, 0x87, 0x74, 0xac,
+  0xdc, 0xd6, 0xfc, 0xc2, 0x9a, 0xbd, 0x33, 0x72, 0x4e, 0x65, 0xb0, 0x5a,
+  0xcd, 0xeb, 0xf8, 0xa9, 0x69, 0x48, 0x39, 0x6e, 0x68, 0x2e, 0x71, 0x16,
+  0x6e, 0x9e, 0x59, 0x7c, 0xc2, 0x7c, 0xcd, 0xed, 0x6e, 0x43, 0xd8, 0x09,
+  0x42, 0x4e, 0x0d, 0x9a, 0x7d, 0xee, 0xb5, 0x5a, 0x23, 0x81, 0xd2, 0xa4,
+  0x5b, 0xa9, 0x51, 0x54, 0x1c, 0xdf, 0xf6, 0x84, 0xdf, 0x19, 0xc3, 0x3e,
+  0x94, 0x2d, 0x8d, 0xba, 0x10, 0xf8, 0xe8, 0x43, 0x08, 0x0f, 0x32, 0x35,
+  0x6c, 0x35, 0x31, 0xf8, 0xd6, 0xd3, 0xfc, 0x09, 0x31, 0xd6, 0xa9, 0xa1,
+  0x7a, 0x20, 0x06, 0x59, 0x0c, 0xe0, 0x2b, 0x8d, 0x84, 0xc3, 0x37, 0xa0,
+  0x08, 0x1e, 0xf1, 0x35, 0x73, 0x10, 0xdd, 0x4f, 0xfd, 0x0c, 0x72, 0x93,
+  0x26, 0x6e, 0xaf, 0xc5, 0x1c, 0x39, 0xe3, 0xca, 0xf3, 0x95, 0x6f, 0x30,
+  0xc2, 0x85, 0x3d, 0x4d, 0x84, 0x20, 0xc8, 0x3e, 0x3d, 0xd0, 0x40, 0xd6,
+  0xfe, 0x06, 0x4a, 0x18, 0x73, 0x0b, 0x6e, 0x57, 0x67, 0xdb, 0x83, 0xc1,
+  0x13, 0x66, 0x97, 0xd3, 0xbd, 0x59, 0xbc, 0x7e, 0xfa, 0x2f, 0x36, 0x45,
+  0x14, 0xcd, 0xbc, 0xbf, 0xab, 0x68, 0x77, 0xbf, 0x48, 0xeb, 0x11, 0x89,
+  0x4e, 0x6a, 0x84, 0xf3, 0x5d, 0x1c, 0xe5, 0x6b, 0x6a, 0x00, 0xe6, 0x6b,
+  0x8d, 0x48, 0xa4, 0x09, 0xb9, 0x21, 0xdc, 0x2d, 0x66, 0x29, 0xf4, 0x56,
+  0x9e, 0xf0, 0x05, 0x68, 0xff, 0xcc, 0xc1, 0xc9, 0x88, 0xbc, 0xd2, 0x2c,
+  0x0b, 0xaf, 0x1d, 0x74, 0x1a, 0x86, 0x68, 0xa4, 0x6d, 0x14, 0x74, 0xec,
+  0x24, 0x80, 0xf8, 0x95, 0xb9, 0xf3, 0x2e, 0x3c, 0x3d, 0x20, 0x6f, 0x09,
+  0x02, 0x38, 0xea, 0x3a, 0x38, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x81, 0xe5, 0x30, 0x81, 0xe2, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x61, 0xa6, 0x99, 0x6d,
+  0x24, 0x9f, 0x0e, 0x11, 0x88, 0xe6, 0x39, 0xe0, 0xfe, 0x74, 0xd1, 0x05,
+  0x69, 0x52, 0xa9, 0x43, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x00, 0xad, 0xd9, 0xa3, 0xf6, 0x79, 0xf6,
+  0x6e, 0x74, 0xa9, 0x7f, 0x33, 0x3d, 0x81, 0x17, 0xd7, 0x4c, 0xcf, 0x33,
+  0xde, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+  0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x48, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x41, 0x30, 0x3f, 0x30, 0x3d, 0x06, 0x04, 0x55, 0x1d, 0x20,
+  0x00, 0x30, 0x35, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x02, 0x01, 0x16, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+  0x2f, 0x70, 0x6b, 0x69, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x61, 0x6f,
+  0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x4f, 0x4c, 0x2f, 0x69, 0x6e,
+  0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x30, 0x35, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0,
+  0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x61, 0x6f, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x4f,
+  0x4c, 0x2f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x52, 0x4c, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x0c,
+  0x9d, 0xbc, 0xcc, 0x1d, 0xd4, 0x2e, 0x91, 0x1b, 0xaf, 0xa0, 0x3a, 0xeb,
+  0xcd, 0x5e, 0xfc, 0x25, 0x2a, 0x29, 0x8e, 0xb3, 0x20, 0xe0, 0x17, 0x37,
+  0xfa, 0xfc, 0xbb, 0xc5, 0xb5, 0x14, 0xbb, 0x1c, 0x66, 0x0e, 0x6f, 0x58,
+  0x5f, 0xc6, 0x71, 0xd0, 0x13, 0x89, 0xc7, 0xad, 0x23, 0x0b, 0xed, 0x4c,
+  0xb8, 0x58, 0xc1, 0xe3, 0xc2, 0xa4, 0x24, 0x4c, 0x65, 0x76, 0x0d, 0xb3,
+  0x86, 0x64, 0x4f, 0x28, 0xba, 0xcf, 0x96, 0xf8, 0x65, 0x9a, 0x0e, 0x82,
+  0x26, 0xf5, 0x82, 0x85, 0x4e, 0x35, 0x20, 0xb3, 0x45, 0xcc, 0x60, 0xee,
+  0x0f, 0x4e, 0x20, 0x94, 0x3a, 0x2b, 0x2f, 0xcb, 0x23, 0x10, 0x9b, 0x46,
+  0x1b, 0x7e, 0xc3, 0x56, 0x75, 0x49, 0x24, 0xa4, 0xb8, 0x4d, 0x9f, 0x1c,
+  0x68, 0xd4, 0xe6, 0xf2, 0x2f, 0xaf, 0x8e, 0xed, 0x2b, 0xb7, 0xe5, 0x96,
+  0x6b, 0x1c, 0x3d, 0x8d, 0xbf, 0x20, 0xd3, 0x6f, 0x2d, 0x54, 0x2f, 0x9c,
+  0x79, 0x35, 0xfd, 0xda, 0x06, 0xde, 0x68, 0x20, 0x20, 0x4b, 0xaf, 0x5d,
+  0xab, 0x5e, 0x66, 0xc3, 0x14, 0x64, 0x7b, 0xf7, 0x02, 0xe6, 0x27, 0x96,
+  0xad, 0x18, 0x1e, 0xab, 0xf3, 0x82, 0x60, 0xfc, 0x4c, 0x5f, 0xb6, 0x0a,
+  0x52, 0x7b, 0x9e, 0x9c, 0x3b, 0x2e, 0xce, 0x3c, 0x42, 0x5f, 0x36, 0x6d,
+  0x6b, 0xfe, 0xa1, 0x76, 0x8a, 0x22, 0x21, 0xfd, 0x5b, 0xe8, 0xbd, 0x7f,
+  0x9f, 0xce, 0x51, 0x74, 0x48, 0x6c, 0xac, 0xb5, 0xd1, 0xa2, 0x6a, 0xfa,
+  0x07, 0x44, 0xde, 0xd0, 0xdb, 0xa9, 0x8d, 0x18, 0x1f, 0xf1, 0xb9, 0xc5,
+  0xe8, 0x2a, 0xeb, 0xba, 0x3d, 0x3b, 0x18, 0x8c, 0xc0, 0x0c, 0x30, 0xb3,
+  0xc9, 0x21, 0x1c, 0x33, 0x4c, 0x3a, 0x49, 0x53, 0xd4, 0xa8, 0xba, 0xba,
+  0x38, 0x23, 0x3d, 0x3a, 0x65, 0x82, 0x5e, 0x79, 0x71, 0x15, 0xf8, 0x25,
+  0x2b, 0x7d, 0x19,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            11:20:96:f6:c8:03:7c:9e:07:b1:38:bf:2e:72:10:8a:d7:ed
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=FR, O=Certplus, CN=Class 2 Primary CA
+        Validity
+            Not Before: Jun  5 00:00:00 2007 GMT
+            Not After : Jun 20 00:00:00 2019 GMT
+        Subject: C=FR, O=KEYNECTIS, CN=CLASS 2 KEYNECTIS CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:be:fe:44:23:04:d4:ef:2f:3b:86:aa:35:58:
+                    81:d1:e1:9a:d6:b1:d4:27:45:28:fc:d1:1e:46:85:
+                    ba:54:23:11:7d:e0:66:3f:d4:a3:57:66:78:f9:6b:
+                    eb:74:7c:2a:b8:37:a5:e8:70:ae:82:b5:4e:d4:81:
+                    fe:5b:e2:ea:e7:22:16:f8:f9:d7:ba:3a:f6:88:56:
+                    dc:c4:f2:a0:a4:e5:75:06:60:72:2b:fb:f5:94:ee:
+                    2c:83:28:de:91:9a:b3:83:3a:b0:9f:08:fa:dd:d8:
+                    9e:8c:24:e6:df:66:5b:c8:7e:a3:62:4d:3f:3a:85:
+                    23:ec:e8:71:8f:0a:00:ac:89:6d:7e:d8:72:e5:dd:
+                    c1:94:8e:5f:e4:73:e6:c1:c6:0c:87:58:4f:37:da:
+                    d1:a9:88:26:76:b4:ee:11:8d:f6:ad:b2:a7:bc:73:
+                    c4:cd:1c:6e:1a:e6:8d:72:56:44:a0:98:f7:92:f9:
+                    d7:79:9b:03:e6:68:5f:a4:5c:7c:3d:50:b4:83:cc:
+                    e5:ac:0d:e1:3e:4f:14:f2:b4:e4:7d:bf:71:a4:c3:
+                    97:73:38:d6:52:7c:c8:a4:b5:ea:e9:b2:54:56:d4:
+                    eb:b8:57:3a:40:52:5a:5e:46:27:a3:7b:30:2d:08:
+                    3d:85:1e:9a:f0:32:a8:f2:10:a2:83:9b:e2:28:f6:
+                    9d:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.4.1.22234.2.5.3.3
+                  CPS: http://www.keynectis.com/PC
+                Policy: 1.3.6.4.1.22234.2.5.1.3
+                  CPS: http://www.keynectis.com/PC
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.certplus.com/CRL/class2.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                00:11:41:DF:3B:9D:3B:CB:B8:A2:C1:33:92:A8:81:CC:E5:7D:E7:99
+            X509v3 Authority Key Identifier: 
+                keyid:E3:73:2D:DF:CB:0E:28:0C:DE:DD:B3:A4:CA:79:B8:8E:BB:E8:30:89
+
+    Signature Algorithm: sha1WithRSAEncryption
+         08:88:fe:1f:a2:ca:cd:e2:a0:f1:2e:7c:67:49:fb:dc:94:ac:
+         7f:41:0d:78:01:ba:31:f7:9b:fb:31:18:77:2f:66:25:94:b8:
+         6d:16:74:81:f1:c0:ae:67:c6:14:45:7a:01:d1:13:88:fc:e2:
+         8d:22:1d:bd:1e:0c:c7:a9:7e:d0:c3:97:f6:37:5b:41:5e:67:
+         94:8e:ab:69:02:17:18:f5:4d:38:c2:49:28:09:6e:5a:9b:a6:
+         27:db:c0:5f:8f:44:9c:90:65:99:d8:b3:2e:c1:92:ee:1a:9d:
+         0f:72:45:20:fa:2c:0c:9c:5d:cd:5b:54:41:54:4f:d3:e2:c7:
+         59:84:3f:17:7b:7d:0e:c2:ef:62:c7:ba:b1:26:6c:83:4e:d3:
+         19:c5:ff:56:a7:b4:45:3f:7a:9e:fa:d0:39:3e:80:46:75:5d:
+         5a:79:7a:33:c5:01:bc:02:44:ce:1b:c0:31:4e:47:96:15:6e:
+         e7:e4:76:f0:c2:90:0d:a1:78:f4:38:00:91:2b:65:7c:79:13:
+         a8:3e:91:14:dc:88:05:08:d7:6f:53:f6:15:43:ee:c5:53:56:
+         1a:02:b5:a6:a2:46:8d:1e:13:e4:67:c2:45:5f:40:5e:10:42:
+         58:b5:cd:44:a3:94:4c:1c:54:90:4d:91:9a:26:8b:ad:a2:80:
+         50:8d:14:14
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgISESCW9sgDfJ4HsTi/LnIQitftMA0GCSqGSIb3DQEBBQUA
+MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xh
+c3MgMiBQcmltYXJ5IENBMB4XDTA3MDYwNTAwMDAwMFoXDTE5MDYyMDAwMDAwMFow
+QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoTCUtFWU5FQ1RJUzEdMBsGA1UEAxMUQ0xB
+U1MgMiBLRVlORUNUSVMgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDGvv5EIwTU7y87hqo1WIHR4ZrWsdQnRSj80R5GhbpUIxF94GY/1KNXZnj5a+t0
+fCq4N6XocK6CtU7Ugf5b4urnIhb4+de6OvaIVtzE8qCk5XUGYHIr+/WU7iyDKN6R
+mrODOrCfCPrd2J6MJObfZlvIfqNiTT86hSPs6HGPCgCsiW1+2HLl3cGUjl/kc+bB
+xgyHWE832tGpiCZ2tO4Rjfatsqe8c8TNHG4a5o1yVkSgmPeS+dd5mwPmaF+kXHw9
+ULSDzOWsDeE+TxTytOR9v3Gkw5dzONZSfMikterpslRW1Ou4VzpAUlpeRiejezAt
+CD2FHprwMqjyEKKDm+Io9p3LAgMBAAGjggEgMIIBHDASBgNVHRMBAf8ECDAGAQH/
+AgEAMH0GA1UdIAR2MHQwOAYLKwYEAYGtWgIFAwMwKTAnBggrBgEFBQcCARYbaHR0
+cDovL3d3dy5rZXluZWN0aXMuY29tL1BDMDgGCysGBAGBrVoCBQEDMCkwJwYIKwYB
+BQUHAgEWG2h0dHA6Ly93d3cua2V5bmVjdGlzLmNvbS9QQzA3BgNVHR8EMDAuMCyg
+KqAohiZodHRwOi8vd3d3LmNlcnRwbHVzLmNvbS9DUkwvY2xhc3MyLmNybDAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAARQd87nTvLuKLBM5KogczlfeeZMB8GA1Ud
+IwQYMBaAFONzLd/LDigM3t2zpMp5uI676DCJMA0GCSqGSIb3DQEBBQUAA4IBAQAI
+iP4fosrN4qDxLnxnSfvclKx/QQ14Abox95v7MRh3L2YllLhtFnSB8cCuZ8YURXoB
+0ROI/OKNIh29HgzHqX7Qw5f2N1tBXmeUjqtpAhcY9U04wkkoCW5am6Yn28Bfj0Sc
+kGWZ2LMuwZLuGp0PckUg+iwMnF3NW1RBVE/T4sdZhD8Xe30Owu9ix7qxJmyDTtMZ
+xf9Wp7RFP3qe+tA5PoBGdV1aeXozxQG8AkTOG8AxTkeWFW7n5HbwwpANoXj0OACR
+K2V8eROoPpEU3IgFCNdvU/YVQ+7FU1YaArWmokaNHhPkZ8JFX0BeEEJYtc1Eo5RM
+HFSQTZGaJoutooBQjRQU
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert14[] = {
+  0x30, 0x82, 0x04, 0x2b, 0x30, 0x82, 0x03, 0x13, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x12, 0x11, 0x20, 0x96, 0xf6, 0xc8, 0x03, 0x7c, 0x9e, 0x07,
+  0xb1, 0x38, 0xbf, 0x2e, 0x72, 0x10, 0x8a, 0xd7, 0xed, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x30, 0x3d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x46, 0x52, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x08, 0x43, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73, 0x31, 0x1b,
+  0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x43, 0x6c, 0x61,
+  0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
+  0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x36, 0x30,
+  0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39,
+  0x30, 0x36, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30,
+  0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x46, 0x52, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x09, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x53, 0x31, 0x1d,
+  0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x14, 0x43, 0x4c, 0x41,
+  0x53, 0x53, 0x20, 0x32, 0x20, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54,
+  0x49, 0x53, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xc6, 0xbe, 0xfe, 0x44, 0x23, 0x04, 0xd4, 0xef, 0x2f, 0x3b,
+  0x86, 0xaa, 0x35, 0x58, 0x81, 0xd1, 0xe1, 0x9a, 0xd6, 0xb1, 0xd4, 0x27,
+  0x45, 0x28, 0xfc, 0xd1, 0x1e, 0x46, 0x85, 0xba, 0x54, 0x23, 0x11, 0x7d,
+  0xe0, 0x66, 0x3f, 0xd4, 0xa3, 0x57, 0x66, 0x78, 0xf9, 0x6b, 0xeb, 0x74,
+  0x7c, 0x2a, 0xb8, 0x37, 0xa5, 0xe8, 0x70, 0xae, 0x82, 0xb5, 0x4e, 0xd4,
+  0x81, 0xfe, 0x5b, 0xe2, 0xea, 0xe7, 0x22, 0x16, 0xf8, 0xf9, 0xd7, 0xba,
+  0x3a, 0xf6, 0x88, 0x56, 0xdc, 0xc4, 0xf2, 0xa0, 0xa4, 0xe5, 0x75, 0x06,
+  0x60, 0x72, 0x2b, 0xfb, 0xf5, 0x94, 0xee, 0x2c, 0x83, 0x28, 0xde, 0x91,
+  0x9a, 0xb3, 0x83, 0x3a, 0xb0, 0x9f, 0x08, 0xfa, 0xdd, 0xd8, 0x9e, 0x8c,
+  0x24, 0xe6, 0xdf, 0x66, 0x5b, 0xc8, 0x7e, 0xa3, 0x62, 0x4d, 0x3f, 0x3a,
+  0x85, 0x23, 0xec, 0xe8, 0x71, 0x8f, 0x0a, 0x00, 0xac, 0x89, 0x6d, 0x7e,
+  0xd8, 0x72, 0xe5, 0xdd, 0xc1, 0x94, 0x8e, 0x5f, 0xe4, 0x73, 0xe6, 0xc1,
+  0xc6, 0x0c, 0x87, 0x58, 0x4f, 0x37, 0xda, 0xd1, 0xa9, 0x88, 0x26, 0x76,
+  0xb4, 0xee, 0x11, 0x8d, 0xf6, 0xad, 0xb2, 0xa7, 0xbc, 0x73, 0xc4, 0xcd,
+  0x1c, 0x6e, 0x1a, 0xe6, 0x8d, 0x72, 0x56, 0x44, 0xa0, 0x98, 0xf7, 0x92,
+  0xf9, 0xd7, 0x79, 0x9b, 0x03, 0xe6, 0x68, 0x5f, 0xa4, 0x5c, 0x7c, 0x3d,
+  0x50, 0xb4, 0x83, 0xcc, 0xe5, 0xac, 0x0d, 0xe1, 0x3e, 0x4f, 0x14, 0xf2,
+  0xb4, 0xe4, 0x7d, 0xbf, 0x71, 0xa4, 0xc3, 0x97, 0x73, 0x38, 0xd6, 0x52,
+  0x7c, 0xc8, 0xa4, 0xb5, 0xea, 0xe9, 0xb2, 0x54, 0x56, 0xd4, 0xeb, 0xb8,
+  0x57, 0x3a, 0x40, 0x52, 0x5a, 0x5e, 0x46, 0x27, 0xa3, 0x7b, 0x30, 0x2d,
+  0x08, 0x3d, 0x85, 0x1e, 0x9a, 0xf0, 0x32, 0xa8, 0xf2, 0x10, 0xa2, 0x83,
+  0x9b, 0xe2, 0x28, 0xf6, 0x9d, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x12, 0x06, 0x03, 0x55,
+  0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff,
+  0x02, 0x01, 0x00, 0x30, 0x7d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x76,
+  0x30, 0x74, 0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad,
+  0x5a, 0x02, 0x05, 0x03, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e,
+  0x65, 0x63, 0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43,
+  0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad, 0x5a, 0x02,
+  0x05, 0x01, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e, 0x65, 0x63,
+  0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43, 0x30, 0x37,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0xa0,
+  0x2a, 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x63, 0x6c, 0x61,
+  0x73, 0x73, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x00, 0x11,
+  0x41, 0xdf, 0x3b, 0x9d, 0x3b, 0xcb, 0xb8, 0xa2, 0xc1, 0x33, 0x92, 0xa8,
+  0x81, 0xcc, 0xe5, 0x7d, 0xe7, 0x99, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe3, 0x73, 0x2d, 0xdf, 0xcb,
+  0x0e, 0x28, 0x0c, 0xde, 0xdd, 0xb3, 0xa4, 0xca, 0x79, 0xb8, 0x8e, 0xbb,
+  0xe8, 0x30, 0x89, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08,
+  0x88, 0xfe, 0x1f, 0xa2, 0xca, 0xcd, 0xe2, 0xa0, 0xf1, 0x2e, 0x7c, 0x67,
+  0x49, 0xfb, 0xdc, 0x94, 0xac, 0x7f, 0x41, 0x0d, 0x78, 0x01, 0xba, 0x31,
+  0xf7, 0x9b, 0xfb, 0x31, 0x18, 0x77, 0x2f, 0x66, 0x25, 0x94, 0xb8, 0x6d,
+  0x16, 0x74, 0x81, 0xf1, 0xc0, 0xae, 0x67, 0xc6, 0x14, 0x45, 0x7a, 0x01,
+  0xd1, 0x13, 0x88, 0xfc, 0xe2, 0x8d, 0x22, 0x1d, 0xbd, 0x1e, 0x0c, 0xc7,
+  0xa9, 0x7e, 0xd0, 0xc3, 0x97, 0xf6, 0x37, 0x5b, 0x41, 0x5e, 0x67, 0x94,
+  0x8e, 0xab, 0x69, 0x02, 0x17, 0x18, 0xf5, 0x4d, 0x38, 0xc2, 0x49, 0x28,
+  0x09, 0x6e, 0x5a, 0x9b, 0xa6, 0x27, 0xdb, 0xc0, 0x5f, 0x8f, 0x44, 0x9c,
+  0x90, 0x65, 0x99, 0xd8, 0xb3, 0x2e, 0xc1, 0x92, 0xee, 0x1a, 0x9d, 0x0f,
+  0x72, 0x45, 0x20, 0xfa, 0x2c, 0x0c, 0x9c, 0x5d, 0xcd, 0x5b, 0x54, 0x41,
+  0x54, 0x4f, 0xd3, 0xe2, 0xc7, 0x59, 0x84, 0x3f, 0x17, 0x7b, 0x7d, 0x0e,
+  0xc2, 0xef, 0x62, 0xc7, 0xba, 0xb1, 0x26, 0x6c, 0x83, 0x4e, 0xd3, 0x19,
+  0xc5, 0xff, 0x56, 0xa7, 0xb4, 0x45, 0x3f, 0x7a, 0x9e, 0xfa, 0xd0, 0x39,
+  0x3e, 0x80, 0x46, 0x75, 0x5d, 0x5a, 0x79, 0x7a, 0x33, 0xc5, 0x01, 0xbc,
+  0x02, 0x44, 0xce, 0x1b, 0xc0, 0x31, 0x4e, 0x47, 0x96, 0x15, 0x6e, 0xe7,
+  0xe4, 0x76, 0xf0, 0xc2, 0x90, 0x0d, 0xa1, 0x78, 0xf4, 0x38, 0x00, 0x91,
+  0x2b, 0x65, 0x7c, 0x79, 0x13, 0xa8, 0x3e, 0x91, 0x14, 0xdc, 0x88, 0x05,
+  0x08, 0xd7, 0x6f, 0x53, 0xf6, 0x15, 0x43, 0xee, 0xc5, 0x53, 0x56, 0x1a,
+  0x02, 0xb5, 0xa6, 0xa2, 0x46, 0x8d, 0x1e, 0x13, 0xe4, 0x67, 0xc2, 0x45,
+  0x5f, 0x40, 0x5e, 0x10, 0x42, 0x58, 0xb5, 0xcd, 0x44, 0xa3, 0x94, 0x4c,
+  0x1c, 0x54, 0x90, 0x4d, 0x91, 0x9a, 0x26, 0x8b, 0xad, 0xa2, 0x80, 0x50,
+  0x8d, 0x14, 0x14,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1184831531 (0x469f182b)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: Nov 26 20:33:13 2009 GMT
+            Not After : Nov  1 04:00:00 2015 GMT
+        Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, CN=USERTrust Legacy Secure Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d9:4d:20:3a:e6:29:30:86:f2:e9:86:89:76:34:
+                    4e:68:1f:96:44:f7:d1:f9:d6:82:4e:a6:38:9e:ee:
+                    cb:5b:e1:8e:2e:bd:f2:57:80:fd:c9:3f:fc:90:73:
+                    44:bc:8f:bb:57:5b:e5:2d:1f:14:30:75:36:f5:7f:
+                    bc:cf:56:f4:7f:81:ff:ae:91:cd:d8:d2:6a:cb:97:
+                    f9:f7:cd:90:6a:45:2d:c4:bb:a4:85:13:68:57:5f:
+                    ef:29:ba:2a:ca:ea:f5:cc:a4:04:9b:63:cd:00:eb:
+                    fd:ed:8d:dd:23:c6:7b:1e:57:1d:36:7f:1f:08:9a:
+                    0d:61:db:5a:6c:71:02:53:28:c2:fa:8d:fd:ab:bb:
+                    b3:f1:8d:74:4b:df:bd:bd:cc:06:93:63:09:95:c2:
+                    10:7a:9d:25:90:32:9d:01:c2:39:53:b0:e0:15:6b:
+                    c7:d7:74:e5:a4:22:9b:e4:94:ff:84:91:fb:2d:b3:
+                    19:43:2d:93:0f:9c:12:09:e4:67:b9:27:7a:32:ad:
+                    7a:2a:cc:41:58:c0:6e:59:5f:ee:38:2b:17:22:9c:
+                    89:fa:6e:e7:e5:57:35:f4:5a:ed:92:95:93:2d:f9:
+                    cc:24:3f:a5:1c:3d:27:bd:22:03:73:cc:f5:ca:f3:
+                    a9:f4:dc:fe:cf:e9:d0:5c:d0:0f:ab:87:fc:83:fd:
+                    c8:a9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.1.3.4
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/server1.crl
+
+            X509v3 Subject Key Identifier: 
+                AF:A4:40:AF:9F:16:FE:AB:31:FD:FB:D5:97:8B:F5:91:A3:24:86:16
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+    Signature Algorithm: sha1WithRSAEncryption
+         33:46:31:c3:2a:b7:b7:41:0e:aa:8e:93:14:2f:78:c3:4a:8e:
+         16:5a:dc:72:32:94:96:57:9a:ac:bc:55:a8:57:cf:7c:e0:79:
+         62:ff:31:ee:d5:9c:54:d0:c0:fd:87:e2:15:06:9e:be:a2:4a:
+         d0:82:eb:6e:4a:58:6a:d9:1f:11:c0:c8:e3:9e:e3:d6:c5:4f:
+         f7:ff:c3:ef:36:8a:68:aa:b2:50:92:ab:59:9d:ea:5b:27:1f:
+         16:a9:3c:45:5f:eb:a5:2a:5d:56:29:8d:3a:14:0d:12:74:71:
+         be:d6:ab:97:de:92:87:61:21:88:7b:41:46:3d:fc:3d:4f:d0:
+         54:5b
+-----BEGIN CERTIFICATE-----
+MIIELTCCA5agAwIBAgIERp8YKzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wOTEx
+MjYyMDMzMTNaFw0xNTExMDEwNDAwMDBaMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQI
+EwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VS
+VFJVU1QgTmV0d29yazEqMCgGA1UEAxMhVVNFUlRydXN0IExlZ2FjeSBTZWN1cmUg
+U2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2U0gOuYp
+MIby6YaJdjROaB+WRPfR+daCTqY4nu7LW+GOLr3yV4D9yT/8kHNEvI+7V1vlLR8U
+MHU29X+8z1b0f4H/rpHN2NJqy5f5982QakUtxLukhRNoV1/vKboqyur1zKQEm2PN
+AOv97Y3dI8Z7HlcdNn8fCJoNYdtabHECUyjC+o39q7uz8Y10S9+9vcwGk2MJlcIQ
+ep0lkDKdAcI5U7DgFWvH13TlpCKb5JT/hJH7LbMZQy2TD5wSCeRnuSd6Mq16KsxB
+WMBuWV/uOCsXIpyJ+m7n5Vc19FrtkpWTLfnMJD+lHD0nvSIDc8z1yvOp9Nz+z+nQ
+XNAPq4f8g/3IqQIDAQABo4HsMIHpMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8E
+CDAGAQH/AgEAMBkGA1UdIAQSMBAwDgYMKwYBBAGyMQECAQMEMDMGCCsGAQUFBwEB
+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZW50cnVzdC5uZXQwMwYDVR0f
+BCwwKjAooCagJIYiaHR0cDovL2NybC5lbnRydXN0Lm5ldC9zZXJ2ZXIxLmNybDAd
+BgNVHQ4EFgQUr6RAr58W/qsx/fvVl4v1kaMkhhYwHwYDVR0jBBgwFoAU8BdiE1U9
+s/8KAGv7UISX8+1i0BowDQYJKoZIhvcNAQEFBQADgYEAM0Yxwyq3t0EOqo6TFC94
+w0qOFlrccjKUllearLxVqFfPfOB5Yv8x7tWcVNDA/YfiFQaevqJK0ILrbkpYatkf
+EcDI457j1sVP9//D7zaKaKqyUJKrWZ3qWycfFqk8RV/rpSpdVimNOhQNEnRxvtar
+l96Sh2EhiHtBRj38PU/QVFs=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert15[] = {
+  0x30, 0x82, 0x04, 0x2d, 0x30, 0x82, 0x03, 0x96, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x46, 0x9f, 0x18, 0x2b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+  0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+  0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+  0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+  0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x31,
+  0x32, 0x36, 0x32, 0x30, 0x33, 0x33, 0x31, 0x33, 0x5a, 0x17, 0x0d, 0x31,
+  0x35, 0x31, 0x31, 0x30, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a,
+  0x30, 0x7f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+  0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+  0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21,
+  0x55, 0x53, 0x45, 0x52, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4c, 0x65,
+  0x67, 0x61, 0x63, 0x79, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd9, 0x4d, 0x20, 0x3a, 0xe6, 0x29,
+  0x30, 0x86, 0xf2, 0xe9, 0x86, 0x89, 0x76, 0x34, 0x4e, 0x68, 0x1f, 0x96,
+  0x44, 0xf7, 0xd1, 0xf9, 0xd6, 0x82, 0x4e, 0xa6, 0x38, 0x9e, 0xee, 0xcb,
+  0x5b, 0xe1, 0x8e, 0x2e, 0xbd, 0xf2, 0x57, 0x80, 0xfd, 0xc9, 0x3f, 0xfc,
+  0x90, 0x73, 0x44, 0xbc, 0x8f, 0xbb, 0x57, 0x5b, 0xe5, 0x2d, 0x1f, 0x14,
+  0x30, 0x75, 0x36, 0xf5, 0x7f, 0xbc, 0xcf, 0x56, 0xf4, 0x7f, 0x81, 0xff,
+  0xae, 0x91, 0xcd, 0xd8, 0xd2, 0x6a, 0xcb, 0x97, 0xf9, 0xf7, 0xcd, 0x90,
+  0x6a, 0x45, 0x2d, 0xc4, 0xbb, 0xa4, 0x85, 0x13, 0x68, 0x57, 0x5f, 0xef,
+  0x29, 0xba, 0x2a, 0xca, 0xea, 0xf5, 0xcc, 0xa4, 0x04, 0x9b, 0x63, 0xcd,
+  0x00, 0xeb, 0xfd, 0xed, 0x8d, 0xdd, 0x23, 0xc6, 0x7b, 0x1e, 0x57, 0x1d,
+  0x36, 0x7f, 0x1f, 0x08, 0x9a, 0x0d, 0x61, 0xdb, 0x5a, 0x6c, 0x71, 0x02,
+  0x53, 0x28, 0xc2, 0xfa, 0x8d, 0xfd, 0xab, 0xbb, 0xb3, 0xf1, 0x8d, 0x74,
+  0x4b, 0xdf, 0xbd, 0xbd, 0xcc, 0x06, 0x93, 0x63, 0x09, 0x95, 0xc2, 0x10,
+  0x7a, 0x9d, 0x25, 0x90, 0x32, 0x9d, 0x01, 0xc2, 0x39, 0x53, 0xb0, 0xe0,
+  0x15, 0x6b, 0xc7, 0xd7, 0x74, 0xe5, 0xa4, 0x22, 0x9b, 0xe4, 0x94, 0xff,
+  0x84, 0x91, 0xfb, 0x2d, 0xb3, 0x19, 0x43, 0x2d, 0x93, 0x0f, 0x9c, 0x12,
+  0x09, 0xe4, 0x67, 0xb9, 0x27, 0x7a, 0x32, 0xad, 0x7a, 0x2a, 0xcc, 0x41,
+  0x58, 0xc0, 0x6e, 0x59, 0x5f, 0xee, 0x38, 0x2b, 0x17, 0x22, 0x9c, 0x89,
+  0xfa, 0x6e, 0xe7, 0xe5, 0x57, 0x35, 0xf4, 0x5a, 0xed, 0x92, 0x95, 0x93,
+  0x2d, 0xf9, 0xcc, 0x24, 0x3f, 0xa5, 0x1c, 0x3d, 0x27, 0xbd, 0x22, 0x03,
+  0x73, 0xcc, 0xf5, 0xca, 0xf3, 0xa9, 0xf4, 0xdc, 0xfe, 0xcf, 0xe9, 0xd0,
+  0x5c, 0xd0, 0x0f, 0xab, 0x87, 0xfc, 0x83, 0xfd, 0xc8, 0xa9, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x81, 0xec, 0x30, 0x81, 0xe9, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+  0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x19, 0x06,
+  0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30, 0x10, 0x30, 0x0e, 0x06, 0x0c,
+  0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x01, 0x03, 0x04,
+  0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65,
+  0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x73,
+  0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xaf, 0xa4, 0x40,
+  0xaf, 0x9f, 0x16, 0xfe, 0xab, 0x31, 0xfd, 0xfb, 0xd5, 0x97, 0x8b, 0xf5,
+  0x91, 0xa3, 0x24, 0x86, 0x16, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d,
+  0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97, 0xf3, 0xed, 0x62,
+  0xd0, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x33, 0x46, 0x31,
+  0xc3, 0x2a, 0xb7, 0xb7, 0x41, 0x0e, 0xaa, 0x8e, 0x93, 0x14, 0x2f, 0x78,
+  0xc3, 0x4a, 0x8e, 0x16, 0x5a, 0xdc, 0x72, 0x32, 0x94, 0x96, 0x57, 0x9a,
+  0xac, 0xbc, 0x55, 0xa8, 0x57, 0xcf, 0x7c, 0xe0, 0x79, 0x62, 0xff, 0x31,
+  0xee, 0xd5, 0x9c, 0x54, 0xd0, 0xc0, 0xfd, 0x87, 0xe2, 0x15, 0x06, 0x9e,
+  0xbe, 0xa2, 0x4a, 0xd0, 0x82, 0xeb, 0x6e, 0x4a, 0x58, 0x6a, 0xd9, 0x1f,
+  0x11, 0xc0, 0xc8, 0xe3, 0x9e, 0xe3, 0xd6, 0xc5, 0x4f, 0xf7, 0xff, 0xc3,
+  0xef, 0x36, 0x8a, 0x68, 0xaa, 0xb2, 0x50, 0x92, 0xab, 0x59, 0x9d, 0xea,
+  0x5b, 0x27, 0x1f, 0x16, 0xa9, 0x3c, 0x45, 0x5f, 0xeb, 0xa5, 0x2a, 0x5d,
+  0x56, 0x29, 0x8d, 0x3a, 0x14, 0x0d, 0x12, 0x74, 0x71, 0xbe, 0xd6, 0xab,
+  0x97, 0xde, 0x92, 0x87, 0x61, 0x21, 0x88, 0x7b, 0x41, 0x46, 0x3d, 0xfc,
+  0x3d, 0x4f, 0xd0, 0x54, 0x5b,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:37:02
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: O=AlphaSSL, CN=AlphaSSL CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c3:f0:65:88:df:1b:dd:c6:82:87:2f:c9:0b:ba:
+                    54:c6:63:3f:46:75:ac:4b:14:1f:98:72:8b:1c:10:
+                    ff:09:a9:52:6e:2f:65:df:65:84:3f:5f:81:b2:d8:
+                    f1:4f:d7:f0:5a:bb:c9:af:d0:31:dd:26:46:2a:99:
+                    9e:d8:a9:a3:b6:b8:07:c4:c9:71:f7:95:84:ef:d2:
+                    ea:1f:54:a0:e5:be:e4:41:21:56:31:10:64:7d:1e:
+                    63:8e:9c:71:5c:3c:a0:2e:de:67:dc:c8:9a:20:f0:
+                    75:c8:b0:b6:27:81:eb:97:0d:ee:22:45:a5:c2:2f:
+                    34:27:ec:e0:59:12:51:b3:1e:05:e5:38:20:d2:69:
+                    59:7a:59:17:be:1a:4b:39:08:12:79:33:9b:64:68:
+                    fe:58:81:dd:88:0c:6a:ba:59:b4:af:24:4f:61:e0:
+                    ca:fc:17:5a:d2:3c:72:ab:a7:4c:b7:b9:ea:2d:e3:
+                    f4:3f:99:a2:4d:c8:1d:58:f8:7f:53:35:8e:d7:22:
+                    88:b7:61:76:08:13:13:69:66:b0:57:59:13:31:0a:
+                    70:82:2b:93:d7:f6:e2:40:15:d0:1d:01:72:c7:13:
+                    58:6a:5a:ec:19:89:16:3c:e0:c8:8d:86:2a:fa:37:
+                    f0:35:32:dd:ec:e5:fe:80:8e:f7:05:67:b4:8b:42:
+                    75:35
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                14:EA:19:55:F0:0E:0D:32:C6:1F:74:33:B7:8E:66:1A:4C:12:31:1E
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.alphassl.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         06:30:42:9b:cf:49:02:7e:89:e9:f5:83:5a:3d:02:f3:bc:b2:
+         46:de:4a:50:ee:b9:9a:90:73:da:a0:5c:26:ca:82:ac:0e:ad:
+         b3:94:fa:28:2e:b2:e6:49:3f:50:77:0e:95:2f:68:f3:65:3c:
+         9f:14:f2:68:60:92:b6:fc:04:0d:f6:a4:18:a1:69:60:0d:e3:
+         9d:68:5b:bc:9e:0b:38:59:8d:21:da:23:fa:99:8a:09:b9:1f:
+         a7:2e:b5:55:6c:47:e7:41:ec:e6:e2:7f:af:55:44:39:e0:ac:
+         74:ee:65:d3:fa:ab:51:48:30:f1:3e:77:6d:ed:e4:0f:40:98:
+         ee:47:7f:8d:b6:58:27:cd:92:6f:60:23:cc:02:9b:59:28:78:
+         a2:51:9d:d0:4a:9c:e5:93:5e:98:8f:cb:ef:3f:ca:fe:e0:af:
+         a4:c9:5b:6e:40:58:a5:92:2d:bd:5d:65:55:c5:bf:7c:04:41:
+         d9:a4:b5:80:e9:94:60:02:10:38:6a:08:08:d7:53:1c:2d:93:
+         af:c9:13:7b:d4:6c:c4:3a:c4:fb:80:ac:bb:3a:4e:54:7a:cd:
+         4e:b3:3e:ed:f1:fc:11:4e:9f:f5:f3:14:bc:b9:b1:31:ce:f6:
+         aa:2f:a5:f8:c3:e9:66:a9:b2:20:9d:c4:f8:b8:03:62:a7:85:
+         d1:18:63:5b
+-----BEGIN CERTIFICATE-----
+MIIELzCCAxegAwIBAgILBAAAAAABL07hNwIwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMC4xETAPBgNVBAoTCEFscGhhU1NMMRkwFwYDVQQD
+ExBBbHBoYVNTTCBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAw/BliN8b3caChy/JC7pUxmM/RnWsSxQfmHKLHBD/CalSbi9l32WEP1+Bstjx
+T9fwWrvJr9Ax3SZGKpme2KmjtrgHxMlx95WE79LqH1Sg5b7kQSFWMRBkfR5jjpxx
+XDygLt5n3MiaIPB1yLC2J4Hrlw3uIkWlwi80J+zgWRJRsx4F5Tgg0mlZelkXvhpL
+OQgSeTObZGj+WIHdiAxqulm0ryRPYeDK/Bda0jxyq6dMt7nqLeP0P5miTcgdWPh/
+UzWO1yKIt2F2CBMTaWawV1kTMQpwgiuT1/biQBXQHQFyxxNYalrsGYkWPODIjYYq
++jfwNTLd7OX+gI73BWe0i0J1NQIDAQABo4IBIzCCAR8wDgYDVR0PAQH/BAQDAgEG
+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFBTqGVXwDg0yxh90M7eOZhpM
+EjEeMEUGA1UdIAQ+MDwwOgYEVR0gADAyMDAGCCsGAQUFBwIBFiRodHRwczovL3d3
+dy5hbHBoYXNzbC5jb20vcmVwb3NpdG9yeS8wMwYDVR0fBCwwKjAooCagJIYiaHR0
+cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNybDA9BggrBgEFBQcBAQQxMC8w
+LQYIKwYBBQUHMAGGIWh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL3Jvb3RyMTAf
+BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOC
+AQEABjBCm89JAn6J6fWDWj0C87yyRt5KUO65mpBz2qBcJsqCrA6ts5T6KC6y5kk/
+UHcOlS9o82U8nxTyaGCStvwEDfakGKFpYA3jnWhbvJ4LOFmNIdoj+pmKCbkfpy61
+VWxH50Hs5uJ/r1VEOeCsdO5l0/qrUUgw8T53be3kD0CY7kd/jbZYJ82Sb2AjzAKb
+WSh4olGd0Eqc5ZNemI/L7z/K/uCvpMlbbkBYpZItvV1lVcW/fARB2aS1gOmUYAIQ
+OGoICNdTHC2Tr8kTe9RsxDrE+4CsuzpOVHrNTrM+7fH8EU6f9fMUvLmxMc72qi+l
++MPpZqmyIJ3E+LgDYqeF0RhjWw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert16[] = {
+  0x30, 0x82, 0x04, 0x2f, 0x30, 0x82, 0x03, 0x17, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x37, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x2e, 0x31, 0x11, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61,
+  0x53, 0x53, 0x4c, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x10, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x53, 0x53, 0x4c, 0x20, 0x43,
+  0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+  0x01, 0x01, 0x00, 0xc3, 0xf0, 0x65, 0x88, 0xdf, 0x1b, 0xdd, 0xc6, 0x82,
+  0x87, 0x2f, 0xc9, 0x0b, 0xba, 0x54, 0xc6, 0x63, 0x3f, 0x46, 0x75, 0xac,
+  0x4b, 0x14, 0x1f, 0x98, 0x72, 0x8b, 0x1c, 0x10, 0xff, 0x09, 0xa9, 0x52,
+  0x6e, 0x2f, 0x65, 0xdf, 0x65, 0x84, 0x3f, 0x5f, 0x81, 0xb2, 0xd8, 0xf1,
+  0x4f, 0xd7, 0xf0, 0x5a, 0xbb, 0xc9, 0xaf, 0xd0, 0x31, 0xdd, 0x26, 0x46,
+  0x2a, 0x99, 0x9e, 0xd8, 0xa9, 0xa3, 0xb6, 0xb8, 0x07, 0xc4, 0xc9, 0x71,
+  0xf7, 0x95, 0x84, 0xef, 0xd2, 0xea, 0x1f, 0x54, 0xa0, 0xe5, 0xbe, 0xe4,
+  0x41, 0x21, 0x56, 0x31, 0x10, 0x64, 0x7d, 0x1e, 0x63, 0x8e, 0x9c, 0x71,
+  0x5c, 0x3c, 0xa0, 0x2e, 0xde, 0x67, 0xdc, 0xc8, 0x9a, 0x20, 0xf0, 0x75,
+  0xc8, 0xb0, 0xb6, 0x27, 0x81, 0xeb, 0x97, 0x0d, 0xee, 0x22, 0x45, 0xa5,
+  0xc2, 0x2f, 0x34, 0x27, 0xec, 0xe0, 0x59, 0x12, 0x51, 0xb3, 0x1e, 0x05,
+  0xe5, 0x38, 0x20, 0xd2, 0x69, 0x59, 0x7a, 0x59, 0x17, 0xbe, 0x1a, 0x4b,
+  0x39, 0x08, 0x12, 0x79, 0x33, 0x9b, 0x64, 0x68, 0xfe, 0x58, 0x81, 0xdd,
+  0x88, 0x0c, 0x6a, 0xba, 0x59, 0xb4, 0xaf, 0x24, 0x4f, 0x61, 0xe0, 0xca,
+  0xfc, 0x17, 0x5a, 0xd2, 0x3c, 0x72, 0xab, 0xa7, 0x4c, 0xb7, 0xb9, 0xea,
+  0x2d, 0xe3, 0xf4, 0x3f, 0x99, 0xa2, 0x4d, 0xc8, 0x1d, 0x58, 0xf8, 0x7f,
+  0x53, 0x35, 0x8e, 0xd7, 0x22, 0x88, 0xb7, 0x61, 0x76, 0x08, 0x13, 0x13,
+  0x69, 0x66, 0xb0, 0x57, 0x59, 0x13, 0x31, 0x0a, 0x70, 0x82, 0x2b, 0x93,
+  0xd7, 0xf6, 0xe2, 0x40, 0x15, 0xd0, 0x1d, 0x01, 0x72, 0xc7, 0x13, 0x58,
+  0x6a, 0x5a, 0xec, 0x19, 0x89, 0x16, 0x3c, 0xe0, 0xc8, 0x8d, 0x86, 0x2a,
+  0xfa, 0x37, 0xf0, 0x35, 0x32, 0xdd, 0xec, 0xe5, 0xfe, 0x80, 0x8e, 0xf7,
+  0x05, 0x67, 0xb4, 0x8b, 0x42, 0x75, 0x35, 0x02, 0x03, 0x01, 0x00, 0x01,
+  0xa3, 0x82, 0x01, 0x23, 0x30, 0x82, 0x01, 0x1f, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x14, 0xea, 0x19, 0x55, 0xf0,
+  0x0e, 0x0d, 0x32, 0xc6, 0x1f, 0x74, 0x33, 0xb7, 0x8e, 0x66, 0x1a, 0x4c,
+  0x12, 0x31, 0x1e, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3e,
+  0x30, 0x3c, 0x30, 0x3a, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x32,
+  0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+  0x16, 0x24, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+  0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+  0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30,
+  0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72,
+  0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30,
+  0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+  0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+  0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60,
+  0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04,
+  0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x06, 0x30, 0x42, 0x9b, 0xcf, 0x49, 0x02, 0x7e, 0x89,
+  0xe9, 0xf5, 0x83, 0x5a, 0x3d, 0x02, 0xf3, 0xbc, 0xb2, 0x46, 0xde, 0x4a,
+  0x50, 0xee, 0xb9, 0x9a, 0x90, 0x73, 0xda, 0xa0, 0x5c, 0x26, 0xca, 0x82,
+  0xac, 0x0e, 0xad, 0xb3, 0x94, 0xfa, 0x28, 0x2e, 0xb2, 0xe6, 0x49, 0x3f,
+  0x50, 0x77, 0x0e, 0x95, 0x2f, 0x68, 0xf3, 0x65, 0x3c, 0x9f, 0x14, 0xf2,
+  0x68, 0x60, 0x92, 0xb6, 0xfc, 0x04, 0x0d, 0xf6, 0xa4, 0x18, 0xa1, 0x69,
+  0x60, 0x0d, 0xe3, 0x9d, 0x68, 0x5b, 0xbc, 0x9e, 0x0b, 0x38, 0x59, 0x8d,
+  0x21, 0xda, 0x23, 0xfa, 0x99, 0x8a, 0x09, 0xb9, 0x1f, 0xa7, 0x2e, 0xb5,
+  0x55, 0x6c, 0x47, 0xe7, 0x41, 0xec, 0xe6, 0xe2, 0x7f, 0xaf, 0x55, 0x44,
+  0x39, 0xe0, 0xac, 0x74, 0xee, 0x65, 0xd3, 0xfa, 0xab, 0x51, 0x48, 0x30,
+  0xf1, 0x3e, 0x77, 0x6d, 0xed, 0xe4, 0x0f, 0x40, 0x98, 0xee, 0x47, 0x7f,
+  0x8d, 0xb6, 0x58, 0x27, 0xcd, 0x92, 0x6f, 0x60, 0x23, 0xcc, 0x02, 0x9b,
+  0x59, 0x28, 0x78, 0xa2, 0x51, 0x9d, 0xd0, 0x4a, 0x9c, 0xe5, 0x93, 0x5e,
+  0x98, 0x8f, 0xcb, 0xef, 0x3f, 0xca, 0xfe, 0xe0, 0xaf, 0xa4, 0xc9, 0x5b,
+  0x6e, 0x40, 0x58, 0xa5, 0x92, 0x2d, 0xbd, 0x5d, 0x65, 0x55, 0xc5, 0xbf,
+  0x7c, 0x04, 0x41, 0xd9, 0xa4, 0xb5, 0x80, 0xe9, 0x94, 0x60, 0x02, 0x10,
+  0x38, 0x6a, 0x08, 0x08, 0xd7, 0x53, 0x1c, 0x2d, 0x93, 0xaf, 0xc9, 0x13,
+  0x7b, 0xd4, 0x6c, 0xc4, 0x3a, 0xc4, 0xfb, 0x80, 0xac, 0xbb, 0x3a, 0x4e,
+  0x54, 0x7a, 0xcd, 0x4e, 0xb3, 0x3e, 0xed, 0xf1, 0xfc, 0x11, 0x4e, 0x9f,
+  0xf5, 0xf3, 0x14, 0xbc, 0xb9, 0xb1, 0x31, 0xce, 0xf6, 0xaa, 0x2f, 0xa5,
+  0xf8, 0xc3, 0xe9, 0x66, 0xa9, 0xb2, 0x20, 0x9d, 0xc4, 0xf8, 0xb8, 0x03,
+  0x62, 0xa7, 0x85, 0xd1, 0x18, 0x63, 0x5b,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:1e:44:a5:f1:71
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 11 12:00:00 2007 GMT
+            Not After : Apr 11 12:00:00 2017 GMT
+        Subject: OU=Alpha CA, O=Alpha, CN=Alpha CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bb:32:2e:2b:13:fd:f4:24:8a:5b:fa:6b:cd:ab:
+                    9c:b2:b6:0b:89:6a:e6:1f:41:ce:8a:24:42:ff:5c:
+                    af:5f:90:3d:f5:90:b9:73:c7:70:b4:c2:ca:38:d6:
+                    ad:06:15:1a:80:38:1d:79:2b:a5:43:20:e7:b9:fa:
+                    8d:06:22:57:0d:64:b5:d1:4d:ed:24:38:49:b4:6a:
+                    07:d4:33:db:3b:76:38:3f:af:77:69:ef:7a:13:33:
+                    29:7b:40:84:90:35:78:5a:8f:23:29:57:6f:b0:57:
+                    ab:37:99:94:28:cf:d3:c7:57:a5:96:b1:8a:81:2e:
+                    73:80:bd:68:ec:1b:11:17:8a:1e:d8:94:77:4b:76:
+                    91:ea:b4:cc:16:33:03:62:b8:06:1a:65:69:be:ac:
+                    d6:97:1b:a7:b1:27:a1:c0:25:52:2f:49:bc:da:04:
+                    06:ba:b8:b5:a6:a8:e1:cb:25:87:b6:28:d4:89:6b:
+                    34:01:77:1a:b6:ec:de:59:dc:99:bb:5d:dc:8f:84:
+                    c2:b9:62:03:13:63:02:09:9e:e1:09:c8:be:f1:18:
+                    79:71:6d:c9:d0:b5:42:97:ca:f8:34:4d:92:87:c0:
+                    39:fa:5c:21:3d:94:52:04:5a:83:a9:d4:ab:83:05:
+                    28:d8:17:23:24:83:64:9b:21:2f:f8:3b:2b:78:64:
+                    87:93
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                0A:29:FA:AD:AF:4D:FD:FD:5D:7D:76:26:87:AB:AA:5A:AA:74:22:15
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.4146.1.10.10
+                  CPS: http://www.alphassl.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Netscape Cert Type: 
+                SSL CA
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         71:13:44:fe:0b:ce:87:60:b5:26:7d:a2:b7:75:63:6a:72:43:
+         70:40:29:ad:0a:49:c6:88:f7:36:cc:d7:85:93:c2:0a:9a:ff:
+         d7:c9:2e:2d:3b:fa:96:39:2f:7b:f3:1e:6e:82:11:6f:d1:20:
+         c9:f8:5d:54:2f:16:28:42:3a:cc:2c:9f:48:87:a3:ba:76:a4:
+         11:f9:8f:b3:a6:f3:86:3d:62:74:40:e4:2d:94:cd:65:ab:1b:
+         41:09:8b:f6:db:fd:d3:02:3f:7c:c5:a1:25:a8:d2:95:50:df:
+         21:2a:71:3b:b1:67:b7:b2:ec:1f:0e:c1:fe:3f:32:3d:3d:87:
+         68:3a:25:f6:21:9c:5f:5c:9a:1c:10:cc:48:8d:83:76:78:39:
+         57:67:ea:64:7e:71:1d:8e:40:e6:a5:ab:64:32:f7:83:c7:7b:
+         bd:a4:de:dc:83:13:a4:a2:8c:f3:2a:76:e9:1a:70:4a:51:17:
+         b7:6c:26:df:ee:05:c7:4e:5b:da:36:54:a1:49:79:f6:4a:06:
+         0a:e3:01:ea:fe:48:73:0b:3d:9c:b8:28:81:f0:b4:a5:c8:62:
+         9a:11:28:cd:18:d1:07:23:d2:ba:ee:14:db:87:64:ed:2b:aa:
+         7f:1a:bd:0a:77:14:d5:d5:cc:31:12:a2:ef:06:a3:17:c1:e0:
+         18:ab:c7:53
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgILBAAAAAABHkSl8XEwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNzA0MTExMjAw
+MDBaFw0xNzA0MTExMjAwMDBaMDYxETAPBgNVBAsTCEFscGhhIENBMQ4wDAYDVQQK
+EwVBbHBoYTERMA8GA1UEAxMIQWxwaGEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQC7Mi4rE/30JIpb+mvNq5yytguJauYfQc6KJEL/XK9fkD31kLlz
+x3C0wso41q0GFRqAOB15K6VDIOe5+o0GIlcNZLXRTe0kOEm0agfUM9s7djg/r3dp
+73oTMyl7QISQNXhajyMpV2+wV6s3mZQoz9PHV6WWsYqBLnOAvWjsGxEXih7YlHdL
+dpHqtMwWMwNiuAYaZWm+rNaXG6exJ6HAJVIvSbzaBAa6uLWmqOHLJYe2KNSJazQB
+dxq27N5Z3Jm7XdyPhMK5YgMTYwIJnuEJyL7xGHlxbcnQtUKXyvg0TZKHwDn6XCE9
+lFIEWoOp1KuDBSjYFyMkg2SbIS/4Oyt4ZIeTAgMBAAGjggEeMIIBGjAOBgNVHQ8B
+Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUCin6ra9N/f1d
+fXYmh6uqWqp0IhUwSgYDVR0gBEMwQTA/BgorBgEEAaAyAQoKMDEwLwYIKwYBBQUH
+AgEWI2h0dHA6Ly93d3cuYWxwaGFzc2wuY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQs
+MCowKKAmoCSGImh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwEQYJ
+YIZIAYb4QgEBBAQDAgIEMCAGA1UdJQQZMBcGCisGAQQBgjcKAwMGCWCGSAGG+EIE
+ATAfBgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEAcRNE/gvOh2C1Jn2it3VjanJDcEAprQpJxoj3NszXhZPCCpr/18kuLTv6
+ljkve/MeboIRb9EgyfhdVC8WKEI6zCyfSIejunakEfmPs6bzhj1idEDkLZTNZasb
+QQmL9tv90wI/fMWhJajSlVDfISpxO7Fnt7LsHw7B/j8yPT2HaDol9iGcX1yaHBDM
+SI2Ddng5V2fqZH5xHY5A5qWrZDL3g8d7vaTe3IMTpKKM8yp26RpwSlEXt2wm3+4F
+x05b2jZUoUl59koGCuMB6v5Icws9nLgogfC0pchimhEozRjRByPSuu4U24dk7Suq
+fxq9CncU1dXMMRKi7wajF8HgGKvHUw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert17[] = {
+  0x30, 0x82, 0x04, 0x32, 0x30, 0x82, 0x03, 0x1a, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1e, 0x44, 0xa5,
+  0xf1, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31, 0x32, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31,
+  0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x36, 0x31, 0x11, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61,
+  0x20, 0x43, 0x41, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x05, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x11, 0x30, 0x0f, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbb,
+  0x32, 0x2e, 0x2b, 0x13, 0xfd, 0xf4, 0x24, 0x8a, 0x5b, 0xfa, 0x6b, 0xcd,
+  0xab, 0x9c, 0xb2, 0xb6, 0x0b, 0x89, 0x6a, 0xe6, 0x1f, 0x41, 0xce, 0x8a,
+  0x24, 0x42, 0xff, 0x5c, 0xaf, 0x5f, 0x90, 0x3d, 0xf5, 0x90, 0xb9, 0x73,
+  0xc7, 0x70, 0xb4, 0xc2, 0xca, 0x38, 0xd6, 0xad, 0x06, 0x15, 0x1a, 0x80,
+  0x38, 0x1d, 0x79, 0x2b, 0xa5, 0x43, 0x20, 0xe7, 0xb9, 0xfa, 0x8d, 0x06,
+  0x22, 0x57, 0x0d, 0x64, 0xb5, 0xd1, 0x4d, 0xed, 0x24, 0x38, 0x49, 0xb4,
+  0x6a, 0x07, 0xd4, 0x33, 0xdb, 0x3b, 0x76, 0x38, 0x3f, 0xaf, 0x77, 0x69,
+  0xef, 0x7a, 0x13, 0x33, 0x29, 0x7b, 0x40, 0x84, 0x90, 0x35, 0x78, 0x5a,
+  0x8f, 0x23, 0x29, 0x57, 0x6f, 0xb0, 0x57, 0xab, 0x37, 0x99, 0x94, 0x28,
+  0xcf, 0xd3, 0xc7, 0x57, 0xa5, 0x96, 0xb1, 0x8a, 0x81, 0x2e, 0x73, 0x80,
+  0xbd, 0x68, 0xec, 0x1b, 0x11, 0x17, 0x8a, 0x1e, 0xd8, 0x94, 0x77, 0x4b,
+  0x76, 0x91, 0xea, 0xb4, 0xcc, 0x16, 0x33, 0x03, 0x62, 0xb8, 0x06, 0x1a,
+  0x65, 0x69, 0xbe, 0xac, 0xd6, 0x97, 0x1b, 0xa7, 0xb1, 0x27, 0xa1, 0xc0,
+  0x25, 0x52, 0x2f, 0x49, 0xbc, 0xda, 0x04, 0x06, 0xba, 0xb8, 0xb5, 0xa6,
+  0xa8, 0xe1, 0xcb, 0x25, 0x87, 0xb6, 0x28, 0xd4, 0x89, 0x6b, 0x34, 0x01,
+  0x77, 0x1a, 0xb6, 0xec, 0xde, 0x59, 0xdc, 0x99, 0xbb, 0x5d, 0xdc, 0x8f,
+  0x84, 0xc2, 0xb9, 0x62, 0x03, 0x13, 0x63, 0x02, 0x09, 0x9e, 0xe1, 0x09,
+  0xc8, 0xbe, 0xf1, 0x18, 0x79, 0x71, 0x6d, 0xc9, 0xd0, 0xb5, 0x42, 0x97,
+  0xca, 0xf8, 0x34, 0x4d, 0x92, 0x87, 0xc0, 0x39, 0xfa, 0x5c, 0x21, 0x3d,
+  0x94, 0x52, 0x04, 0x5a, 0x83, 0xa9, 0xd4, 0xab, 0x83, 0x05, 0x28, 0xd8,
+  0x17, 0x23, 0x24, 0x83, 0x64, 0x9b, 0x21, 0x2f, 0xf8, 0x3b, 0x2b, 0x78,
+  0x64, 0x87, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1e,
+  0x30, 0x82, 0x01, 0x1a, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x0a, 0x29, 0xfa, 0xad, 0xaf, 0x4d, 0xfd, 0xfd, 0x5d,
+  0x7d, 0x76, 0x26, 0x87, 0xab, 0xaa, 0x5a, 0xaa, 0x74, 0x22, 0x15, 0x30,
+  0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f,
+  0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xa0, 0x32, 0x01, 0x0a, 0x0a,
+  0x30, 0x31, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x73, 0x73, 0x6c, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+  0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+  0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x11, 0x06, 0x09,
+  0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03,
+  0x02, 0x02, 0x04, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x19,
+  0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a,
+  0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+  0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50,
+  0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x71, 0x13, 0x44, 0xfe, 0x0b, 0xce,
+  0x87, 0x60, 0xb5, 0x26, 0x7d, 0xa2, 0xb7, 0x75, 0x63, 0x6a, 0x72, 0x43,
+  0x70, 0x40, 0x29, 0xad, 0x0a, 0x49, 0xc6, 0x88, 0xf7, 0x36, 0xcc, 0xd7,
+  0x85, 0x93, 0xc2, 0x0a, 0x9a, 0xff, 0xd7, 0xc9, 0x2e, 0x2d, 0x3b, 0xfa,
+  0x96, 0x39, 0x2f, 0x7b, 0xf3, 0x1e, 0x6e, 0x82, 0x11, 0x6f, 0xd1, 0x20,
+  0xc9, 0xf8, 0x5d, 0x54, 0x2f, 0x16, 0x28, 0x42, 0x3a, 0xcc, 0x2c, 0x9f,
+  0x48, 0x87, 0xa3, 0xba, 0x76, 0xa4, 0x11, 0xf9, 0x8f, 0xb3, 0xa6, 0xf3,
+  0x86, 0x3d, 0x62, 0x74, 0x40, 0xe4, 0x2d, 0x94, 0xcd, 0x65, 0xab, 0x1b,
+  0x41, 0x09, 0x8b, 0xf6, 0xdb, 0xfd, 0xd3, 0x02, 0x3f, 0x7c, 0xc5, 0xa1,
+  0x25, 0xa8, 0xd2, 0x95, 0x50, 0xdf, 0x21, 0x2a, 0x71, 0x3b, 0xb1, 0x67,
+  0xb7, 0xb2, 0xec, 0x1f, 0x0e, 0xc1, 0xfe, 0x3f, 0x32, 0x3d, 0x3d, 0x87,
+  0x68, 0x3a, 0x25, 0xf6, 0x21, 0x9c, 0x5f, 0x5c, 0x9a, 0x1c, 0x10, 0xcc,
+  0x48, 0x8d, 0x83, 0x76, 0x78, 0x39, 0x57, 0x67, 0xea, 0x64, 0x7e, 0x71,
+  0x1d, 0x8e, 0x40, 0xe6, 0xa5, 0xab, 0x64, 0x32, 0xf7, 0x83, 0xc7, 0x7b,
+  0xbd, 0xa4, 0xde, 0xdc, 0x83, 0x13, 0xa4, 0xa2, 0x8c, 0xf3, 0x2a, 0x76,
+  0xe9, 0x1a, 0x70, 0x4a, 0x51, 0x17, 0xb7, 0x6c, 0x26, 0xdf, 0xee, 0x05,
+  0xc7, 0x4e, 0x5b, 0xda, 0x36, 0x54, 0xa1, 0x49, 0x79, 0xf6, 0x4a, 0x06,
+  0x0a, 0xe3, 0x01, 0xea, 0xfe, 0x48, 0x73, 0x0b, 0x3d, 0x9c, 0xb8, 0x28,
+  0x81, 0xf0, 0xb4, 0xa5, 0xc8, 0x62, 0x9a, 0x11, 0x28, 0xcd, 0x18, 0xd1,
+  0x07, 0x23, 0xd2, 0xba, 0xee, 0x14, 0xdb, 0x87, 0x64, 0xed, 0x2b, 0xaa,
+  0x7f, 0x1a, 0xbd, 0x0a, 0x77, 0x14, 0xd5, 0xd5, 0xcc, 0x31, 0x12, 0xa2,
+  0xef, 0x06, 0xa3, 0x17, 0xc1, 0xe0, 0x18, 0xab, 0xc7, 0x53,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120020006 (0x7275c26)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Validity
+            Not Before: Aug 18 18:36:33 2011 GMT
+            Not After : Aug  9 18:35:49 2018 GMT
+        Subject: C=JP, O=Cybertrust Japan Co., Ltd., CN=Cybertrust Japan Public CA G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:dc:76:fb:b9:44:fb:12:7c:54:b0:bb:41:75:
+                    74:f0:35:47:9e:27:ba:43:44:45:60:19:cd:44:0a:
+                    b2:78:e5:fa:1e:24:3b:02:1f:68:77:60:f4:eb:22:
+                    05:0a:37:6e:db:f7:15:20:bb:3d:53:d4:d0:11:8e:
+                    d4:eb:67:be:d8:dd:05:37:94:41:25:23:ef:9a:10:
+                    a3:c3:f7:00:bd:ba:26:92:67:81:50:a9:4f:9e:91:
+                    3c:4e:20:ba:52:7f:ff:4c:4d:8e:aa:ad:4e:5f:bc:
+                    27:97:02:29:fd:95:83:49:bf:0a:ba:b1:35:18:35:
+                    a9:73:88:62:51:ac:14:17:34:50:e7:9f:c6:f3:93:
+                    5d:b9:c5:00:4f:37:90:7a:81:0f:d4:f5:72:29:e8:
+                    8c:62:ac:71:3b:f3:2e:df:2a:5c:e9:be:e7:56:24:
+                    82:28:9a:bf:3d:fd:71:42:fd:c5:7b:6b:37:09:47:
+                    50:3e:72:91:aa:3b:c5:4f:9d:12:6e:3d:80:03:54:
+                    49:41:15:3c:f2:86:44:74:28:f5:b9:a3:1b:db:76:
+                    c7:ea:03:89:a0:60:e9:51:f6:a1:64:3e:74:9b:db:
+                    2a:cb:18:3c:c1:e7:68:18:89:ec:ab:38:2e:50:b9:
+                    6f:6e:15:33:28:1d:2f:d5:80:17:86:22:f3:1d:0c:
+                    e0:87
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+            X509v3 Subject Key Identifier: 
+                1B:E4:8D:EF:3A:71:6B:12:65:68:CF:B6:91:BC:39:43:01:8D:75:C9
+    Signature Algorithm: sha1WithRSAEncryption
+         6d:2b:ed:e9:8f:b6:29:d6:b6:30:ba:e6:6d:b5:c3:53:d2:c9:
+         b8:0a:4e:48:9f:f1:3f:3a:2a:b4:fb:74:96:e6:bd:18:bc:a7:
+         79:c0:78:c1:39:ba:20:e3:4e:80:d2:24:d5:a5:7b:7f:62:a3:
+         da:d4:0f:0d:a9:9d:aa:c5:e9:bc:08:c9:48:66:98:9e:c0:f9:
+         de:ab:6d:e5:b0:33:c2:70:d0:96:d7:1e:6f:98:04:15:63:48:
+         bb:9a:85:5d:1b:69:f3:b4:4f:f3:a6:70:16:0f:21:19:7f:ad:
+         b0:03:39:49:86:8c:a2:73:4e:93:dc:21:01:68:b0:ef:0d:70:
+         1a:7a:a2:b2:cc:22:42:91:54:bd:ac:70:04:8d:4c:5c:fc:3a:
+         a2:92:f6:f9:2a:12:40:cd:76:b7:66:f4:d7:d2:ab:de:8e:2c:
+         d9:dd:09:35:37:27:56:0b:ae:b6:ae:9a:83:60:f0:5f:7f:f0:
+         ad:b3:1c:fa:b8:74:5f:c7:ec:c0:0e:ad:d8:c3:cf:9f:d2:30:
+         0e:5c:51:1f:3e:19:e2:c1:d2:a9:90:0f:e6:57:b9:af:04:76:
+         a2:f2:08:04:25:0a:35:bb:73:04:31:d0:98:46:7f:36:c1:61:
+         16:a3:8a:8c:99:de:10:05:f2:de:ed:a5:09:64:97:84:8f:42:
+         a8:7b:22:c4
+-----BEGIN CERTIFICATE-----
+MIIENDCCAxygAwIBAgIEBydcJjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTExMDgxODE4MzYzM1oX
+DTE4MDgwOTE4MzU0OVowWjELMAkGA1UEBhMCSlAxIzAhBgNVBAoMGkN5YmVydHJ1
+c3QgSmFwYW4gQ28uLCBMdGQuMSYwJAYDVQQDDB1DeWJlcnRydXN0IEphcGFuIFB1
+YmxpYyBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALbcdvu5
+RPsSfFSwu0F1dPA1R54nukNERWAZzUQKsnjl+h4kOwIfaHdg9OsiBQo3btv3FSC7
+PVPU0BGO1OtnvtjdBTeUQSUj75oQo8P3AL26JpJngVCpT56RPE4gulJ//0xNjqqt
+Tl+8J5cCKf2Vg0m/CrqxNRg1qXOIYlGsFBc0UOefxvOTXbnFAE83kHqBD9T1cino
+jGKscTvzLt8qXOm+51Ykgiiavz39cUL9xXtrNwlHUD5ykao7xU+dEm49gANUSUEV
+PPKGRHQo9bmjG9t2x+oDiaBg6VH2oWQ+dJvbKssYPMHnaBiJ7Ks4LlC5b24VMygd
+L9WAF4Yi8x0M4IcCAwEAAaOCAQAwgf0wEgYDVR0TAQH/BAgwBgEB/wIBADBTBgNV
+HSAETDBKMEgGCSsGAQQBsT4BADA7MDkGCCsGAQUFBwIBFi1odHRwOi8vY3liZXJ0
+cnVzdC5vbW5pcm9vdC5jb20vcmVwb3NpdG9yeS5jZm0wDgYDVR0PAQH/BAQDAgEG
+MB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1BE3wMEIGA1UdHwQ7MDkwN6A1
+oDOGMWh0dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5jb20vQ1JML09tbmlyb290MjAy
+NS5jcmwwHQYDVR0OBBYEFBvkje86cWsSZWjPtpG8OUMBjXXJMA0GCSqGSIb3DQEB
+BQUAA4IBAQBtK+3pj7Yp1rYwuuZttcNT0sm4Ck5In/E/Oiq0+3SW5r0YvKd5wHjB
+Obog406A0iTVpXt/YqPa1A8NqZ2qxem8CMlIZpiewPneq23lsDPCcNCW1x5vmAQV
+Y0i7moVdG2nztE/zpnAWDyEZf62wAzlJhoyic06T3CEBaLDvDXAaeqKyzCJCkVS9
+rHAEjUxc/Dqikvb5KhJAzXa3ZvTX0qvejizZ3Qk1NydWC662rpqDYPBff/Ctsxz6
+uHRfx+zADq3Yw8+f0jAOXFEfPhniwdKpkA/mV7mvBHai8ggEJQo1u3MEMdCYRn82
+wWEWo4qMmd4QBfLe7aUJZJeEj0KoeyLE
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert18[] = {
+  0x30, 0x82, 0x04, 0x34, 0x30, 0x82, 0x03, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x5c, 0x26, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+  0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+  0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31,
+  0x30, 0x38, 0x31, 0x38, 0x31, 0x38, 0x33, 0x36, 0x33, 0x33, 0x5a, 0x17,
+  0x0d, 0x31, 0x38, 0x30, 0x38, 0x30, 0x39, 0x31, 0x38, 0x33, 0x35, 0x34,
+  0x39, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+  0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x0c, 0x1a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x43, 0x6f, 0x2e,
+  0x2c, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x0c, 0x1d, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x50, 0x75,
+  0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x47, 0x32, 0x30, 0x82,
+  0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+  0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0xdc, 0x76, 0xfb, 0xb9,
+  0x44, 0xfb, 0x12, 0x7c, 0x54, 0xb0, 0xbb, 0x41, 0x75, 0x74, 0xf0, 0x35,
+  0x47, 0x9e, 0x27, 0xba, 0x43, 0x44, 0x45, 0x60, 0x19, 0xcd, 0x44, 0x0a,
+  0xb2, 0x78, 0xe5, 0xfa, 0x1e, 0x24, 0x3b, 0x02, 0x1f, 0x68, 0x77, 0x60,
+  0xf4, 0xeb, 0x22, 0x05, 0x0a, 0x37, 0x6e, 0xdb, 0xf7, 0x15, 0x20, 0xbb,
+  0x3d, 0x53, 0xd4, 0xd0, 0x11, 0x8e, 0xd4, 0xeb, 0x67, 0xbe, 0xd8, 0xdd,
+  0x05, 0x37, 0x94, 0x41, 0x25, 0x23, 0xef, 0x9a, 0x10, 0xa3, 0xc3, 0xf7,
+  0x00, 0xbd, 0xba, 0x26, 0x92, 0x67, 0x81, 0x50, 0xa9, 0x4f, 0x9e, 0x91,
+  0x3c, 0x4e, 0x20, 0xba, 0x52, 0x7f, 0xff, 0x4c, 0x4d, 0x8e, 0xaa, 0xad,
+  0x4e, 0x5f, 0xbc, 0x27, 0x97, 0x02, 0x29, 0xfd, 0x95, 0x83, 0x49, 0xbf,
+  0x0a, 0xba, 0xb1, 0x35, 0x18, 0x35, 0xa9, 0x73, 0x88, 0x62, 0x51, 0xac,
+  0x14, 0x17, 0x34, 0x50, 0xe7, 0x9f, 0xc6, 0xf3, 0x93, 0x5d, 0xb9, 0xc5,
+  0x00, 0x4f, 0x37, 0x90, 0x7a, 0x81, 0x0f, 0xd4, 0xf5, 0x72, 0x29, 0xe8,
+  0x8c, 0x62, 0xac, 0x71, 0x3b, 0xf3, 0x2e, 0xdf, 0x2a, 0x5c, 0xe9, 0xbe,
+  0xe7, 0x56, 0x24, 0x82, 0x28, 0x9a, 0xbf, 0x3d, 0xfd, 0x71, 0x42, 0xfd,
+  0xc5, 0x7b, 0x6b, 0x37, 0x09, 0x47, 0x50, 0x3e, 0x72, 0x91, 0xaa, 0x3b,
+  0xc5, 0x4f, 0x9d, 0x12, 0x6e, 0x3d, 0x80, 0x03, 0x54, 0x49, 0x41, 0x15,
+  0x3c, 0xf2, 0x86, 0x44, 0x74, 0x28, 0xf5, 0xb9, 0xa3, 0x1b, 0xdb, 0x76,
+  0xc7, 0xea, 0x03, 0x89, 0xa0, 0x60, 0xe9, 0x51, 0xf6, 0xa1, 0x64, 0x3e,
+  0x74, 0x9b, 0xdb, 0x2a, 0xcb, 0x18, 0x3c, 0xc1, 0xe7, 0x68, 0x18, 0x89,
+  0xec, 0xab, 0x38, 0x2e, 0x50, 0xb9, 0x6f, 0x6e, 0x15, 0x33, 0x28, 0x1d,
+  0x2f, 0xd5, 0x80, 0x17, 0x86, 0x22, 0xf3, 0x1d, 0x0c, 0xe0, 0x87, 0x02,
+  0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x00, 0x30, 0x81, 0xfd, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x53, 0x06, 0x03, 0x55,
+  0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06,
+  0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
+  0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08,
+  0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35,
+  0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c,
+  0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32,
+  0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0x1b, 0xe4, 0x8d, 0xef, 0x3a, 0x71, 0x6b, 0x12,
+  0x65, 0x68, 0xcf, 0xb6, 0x91, 0xbc, 0x39, 0x43, 0x01, 0x8d, 0x75, 0xc9,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x6d, 0x2b, 0xed, 0xe9,
+  0x8f, 0xb6, 0x29, 0xd6, 0xb6, 0x30, 0xba, 0xe6, 0x6d, 0xb5, 0xc3, 0x53,
+  0xd2, 0xc9, 0xb8, 0x0a, 0x4e, 0x48, 0x9f, 0xf1, 0x3f, 0x3a, 0x2a, 0xb4,
+  0xfb, 0x74, 0x96, 0xe6, 0xbd, 0x18, 0xbc, 0xa7, 0x79, 0xc0, 0x78, 0xc1,
+  0x39, 0xba, 0x20, 0xe3, 0x4e, 0x80, 0xd2, 0x24, 0xd5, 0xa5, 0x7b, 0x7f,
+  0x62, 0xa3, 0xda, 0xd4, 0x0f, 0x0d, 0xa9, 0x9d, 0xaa, 0xc5, 0xe9, 0xbc,
+  0x08, 0xc9, 0x48, 0x66, 0x98, 0x9e, 0xc0, 0xf9, 0xde, 0xab, 0x6d, 0xe5,
+  0xb0, 0x33, 0xc2, 0x70, 0xd0, 0x96, 0xd7, 0x1e, 0x6f, 0x98, 0x04, 0x15,
+  0x63, 0x48, 0xbb, 0x9a, 0x85, 0x5d, 0x1b, 0x69, 0xf3, 0xb4, 0x4f, 0xf3,
+  0xa6, 0x70, 0x16, 0x0f, 0x21, 0x19, 0x7f, 0xad, 0xb0, 0x03, 0x39, 0x49,
+  0x86, 0x8c, 0xa2, 0x73, 0x4e, 0x93, 0xdc, 0x21, 0x01, 0x68, 0xb0, 0xef,
+  0x0d, 0x70, 0x1a, 0x7a, 0xa2, 0xb2, 0xcc, 0x22, 0x42, 0x91, 0x54, 0xbd,
+  0xac, 0x70, 0x04, 0x8d, 0x4c, 0x5c, 0xfc, 0x3a, 0xa2, 0x92, 0xf6, 0xf9,
+  0x2a, 0x12, 0x40, 0xcd, 0x76, 0xb7, 0x66, 0xf4, 0xd7, 0xd2, 0xab, 0xde,
+  0x8e, 0x2c, 0xd9, 0xdd, 0x09, 0x35, 0x37, 0x27, 0x56, 0x0b, 0xae, 0xb6,
+  0xae, 0x9a, 0x83, 0x60, 0xf0, 0x5f, 0x7f, 0xf0, 0xad, 0xb3, 0x1c, 0xfa,
+  0xb8, 0x74, 0x5f, 0xc7, 0xec, 0xc0, 0x0e, 0xad, 0xd8, 0xc3, 0xcf, 0x9f,
+  0xd2, 0x30, 0x0e, 0x5c, 0x51, 0x1f, 0x3e, 0x19, 0xe2, 0xc1, 0xd2, 0xa9,
+  0x90, 0x0f, 0xe6, 0x57, 0xb9, 0xaf, 0x04, 0x76, 0xa2, 0xf2, 0x08, 0x04,
+  0x25, 0x0a, 0x35, 0xbb, 0x73, 0x04, 0x31, 0xd0, 0x98, 0x46, 0x7f, 0x36,
+  0xc1, 0x61, 0x16, 0xa3, 0x8a, 0x8c, 0x99, 0xde, 0x10, 0x05, 0xf2, 0xde,
+  0xed, 0xa5, 0x09, 0x64, 0x97, 0x84, 0x8f, 0x42, 0xa8, 0x7b, 0x22, 0xc4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 314159292 (0x12b9b0bc)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1
+        Validity
+            Not Before: Feb 28 08:51:17 2008 GMT
+            Not After : Feb 28 08:51:17 2018 GMT
+        Subject: C=JP, O=SECOM Trust Systems CO.,LTD., CN=SECOM Passport for Web SR 2.0 CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ad:81:dd:77:fa:50:b2:3f:38:54:f7:2b:6a:04:
+                    23:1d:db:84:08:6d:dc:51:41:bf:09:01:d7:84:af:
+                    c8:7e:92:f9:72:47:35:6a:d5:85:eb:2d:5a:82:72:
+                    6f:6c:10:e6:37:9d:fc:d7:0a:5f:99:07:24:3d:50:
+                    bb:36:f7:d1:50:2d:38:5d:bf:6b:56:2e:e6:79:b4:
+                    59:3e:a0:09:7e:d0:8e:a8:0c:ed:0a:c3:5b:f8:79:
+                    b5:c1:23:5a:b3:b5:f3:f2:f6:e4:e4:2a:a9:ff:86:
+                    53:3e:57:cc:9d:00:36:57:f1:c2:af:34:2b:52:68:
+                    57:4d:a1:57:cb:28:c6:21:ba:58:4a:99:a7:65:e5:
+                    85:24:3f:23:42:e7:cb:96:a5:03:97:3f:ab:ea:40:
+                    57:6e:1f:a7:4e:24:25:4e:ee:92:8b:f1:a5:e4:8f:
+                    dc:96:56:54:01:17:8c:42:07:df:de:d3:c2:c2:7e:
+                    97:5d:9d:25:f3:e5:04:ee:6e:3b:4f:23:25:ce:4c:
+                    75:e0:e4:e9:19:ab:55:2e:ad:8d:cb:b9:8b:c2:25:
+                    91:4e:09:78:af:35:3c:5f:b1:a7:40:9d:e1:12:0b:
+                    83:e0:7a:c8:f4:af:81:a6:3c:43:b0:e6:59:9b:cc:
+                    66:27:75:49:6e:39:ef:8b:22:ed:9b:99:a4:94:94:
+                    8e:09
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                30:9A:00:57:99:44:63:6B:C9:B2:F2:3D:8D:83:6B:3B:D7:9D:EF:64
+            X509v3 Authority Key Identifier: 
+                keyid:A0:73:49:99:68:DC:85:5B:65:E3:9B:28:2F:57:9F:BD:33:BC:07:48
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://repository.secomtrust.net/SC-Root1/SCRoot1CRL.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 1.2.392.200091.100.901.1
+                  CPS: https://repository.secomtrust.net/SC-Root1/
+
+    Signature Algorithm: sha1WithRSAEncryption
+         4d:b3:4b:19:c4:de:f8:9e:7b:54:3a:69:77:b8:31:ed:50:dc:
+         f4:d2:ad:2f:08:5b:af:77:c1:23:be:14:38:39:fd:2d:4e:d1:
+         d2:29:b6:9d:ad:8e:79:76:41:1a:d5:4c:dd:b6:d1:54:68:bb:
+         8c:2d:b1:d9:18:77:03:bb:46:91:1a:28:ec:e1:b8:00:2d:8c:
+         b5:0c:a2:24:df:75:ba:58:fc:dd:8a:c1:b4:53:12:29:43:77:
+         ed:4c:73:8d:b1:49:1f:14:56:be:11:8d:6d:d6:55:35:55:25:
+         48:8a:84:e8:3e:74:38:e2:7d:ba:37:64:69:14:a7:69:d9:c6:
+         40:3e:74:b4:23:bb:c2:71:9f:2b:3d:6a:88:81:11:ce:2e:bc:
+         12:9f:c8:18:e7:f9:63:0d:25:87:c3:85:05:ba:9c:9d:b7:a7:
+         69:41:ee:c8:67:82:d3:2f:03:e6:43:c3:53:8a:60:58:d6:2a:
+         1a:c0:80:9a:97:89:54:40:e6:25:c5:1e:8e:af:7f:a1:10:bf:
+         37:13:05:1d:8a:d0:42:18:f8:bb:f9:64:a8:05:56:06:fa:27:
+         71:0f:5c:79:90:ff:5a:43:a2:a7:b7:6c:68:64:8a:94:25:ee:
+         be:7f:7b:27:0c:92:4b:99:c5:33:3d:93:e0:62:71:29:81:cb:
+         26:7a:a7:c8
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIEErmwvDANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK
+UDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBD
+b21tdW5pY2F0aW9uIFJvb3RDQTEwHhcNMDgwMjI4MDg1MTE3WhcNMTgwMjI4MDg1
+MTE3WjBfMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVt
+cyBDTy4sTFRELjEpMCcGA1UEAxMgU0VDT00gUGFzc3BvcnQgZm9yIFdlYiBTUiAy
+LjAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtgd13+lCyPzhU
+9ytqBCMd24QIbdxRQb8JAdeEr8h+kvlyRzVq1YXrLVqCcm9sEOY3nfzXCl+ZByQ9
+ULs299FQLThdv2tWLuZ5tFk+oAl+0I6oDO0Kw1v4ebXBI1qztfPy9uTkKqn/hlM+
+V8ydADZX8cKvNCtSaFdNoVfLKMYhulhKmadl5YUkPyNC58uWpQOXP6vqQFduH6dO
+JCVO7pKL8aXkj9yWVlQBF4xCB9/e08LCfpddnSXz5QTubjtPIyXOTHXg5OkZq1Uu
+rY3LuYvCJZFOCXivNTxfsadAneESC4Pgesj0r4GmPEOw5lmbzGYndUluOe+LIu2b
+maSUlI4JAgMBAAGjggEHMIIBAzAdBgNVHQ4EFgQUMJoAV5lEY2vJsvI9jYNrO9ed
+72QwHwYDVR0jBBgwFoAUoHNJmWjchVtl45soL1efvTO8B0gwEgYDVR0TAQH/BAgw
+BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDov
+L3JlcG9zaXRvcnkuc2Vjb210cnVzdC5uZXQvU0MtUm9vdDEvU0NSb290MUNSTC5j
+cmwwUgYDVR0gBEswSTBHBgoqgwiMmxtkhwUBMDkwNwYIKwYBBQUHAgEWK2h0dHBz
+Oi8vcmVwb3NpdG9yeS5zZWNvbXRydXN0Lm5ldC9TQy1Sb290MS8wDQYJKoZIhvcN
+AQEFBQADggEBAE2zSxnE3viee1Q6aXe4Me1Q3PTSrS8IW693wSO+FDg5/S1O0dIp
+tp2tjnl2QRrVTN220VRou4wtsdkYdwO7RpEaKOzhuAAtjLUMoiTfdbpY/N2KwbRT
+EilDd+1Mc42xSR8UVr4RjW3WVTVVJUiKhOg+dDjifbo3ZGkUp2nZxkA+dLQju8Jx
+nys9aoiBEc4uvBKfyBjn+WMNJYfDhQW6nJ23p2lB7shngtMvA+ZDw1OKYFjWKhrA
+gJqXiVRA5iXFHo6vf6EQvzcTBR2K0EIY+Lv5ZKgFVgb6J3EPXHmQ/1pDoqe3bGhk
+ipQl7r5/eycMkkuZxTM9k+BicSmByyZ6p8g=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert19[] = {
+  0x30, 0x82, 0x04, 0x36, 0x30, 0x82, 0x03, 0x1e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x12, 0xb9, 0xb0, 0xbc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x50,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a,
+  0x50, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x20, 0x43,
+  0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x31, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x38, 0x30, 0x32, 0x32, 0x38, 0x30, 0x38, 0x35, 0x31, 0x31, 0x37,
+  0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x32, 0x32, 0x38, 0x30, 0x38, 0x35,
+  0x31, 0x31, 0x37, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x25, 0x30, 0x23, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+  0x73, 0x20, 0x43, 0x4f, 0x2e, 0x2c, 0x4c, 0x54, 0x44, 0x2e, 0x31, 0x29,
+  0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x45, 0x43,
+  0x4f, 0x4d, 0x20, 0x50, 0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x20,
+  0x66, 0x6f, 0x72, 0x20, 0x57, 0x65, 0x62, 0x20, 0x53, 0x52, 0x20, 0x32,
+  0x2e, 0x30, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xad, 0x81, 0xdd, 0x77, 0xfa, 0x50, 0xb2, 0x3f, 0x38, 0x54,
+  0xf7, 0x2b, 0x6a, 0x04, 0x23, 0x1d, 0xdb, 0x84, 0x08, 0x6d, 0xdc, 0x51,
+  0x41, 0xbf, 0x09, 0x01, 0xd7, 0x84, 0xaf, 0xc8, 0x7e, 0x92, 0xf9, 0x72,
+  0x47, 0x35, 0x6a, 0xd5, 0x85, 0xeb, 0x2d, 0x5a, 0x82, 0x72, 0x6f, 0x6c,
+  0x10, 0xe6, 0x37, 0x9d, 0xfc, 0xd7, 0x0a, 0x5f, 0x99, 0x07, 0x24, 0x3d,
+  0x50, 0xbb, 0x36, 0xf7, 0xd1, 0x50, 0x2d, 0x38, 0x5d, 0xbf, 0x6b, 0x56,
+  0x2e, 0xe6, 0x79, 0xb4, 0x59, 0x3e, 0xa0, 0x09, 0x7e, 0xd0, 0x8e, 0xa8,
+  0x0c, 0xed, 0x0a, 0xc3, 0x5b, 0xf8, 0x79, 0xb5, 0xc1, 0x23, 0x5a, 0xb3,
+  0xb5, 0xf3, 0xf2, 0xf6, 0xe4, 0xe4, 0x2a, 0xa9, 0xff, 0x86, 0x53, 0x3e,
+  0x57, 0xcc, 0x9d, 0x00, 0x36, 0x57, 0xf1, 0xc2, 0xaf, 0x34, 0x2b, 0x52,
+  0x68, 0x57, 0x4d, 0xa1, 0x57, 0xcb, 0x28, 0xc6, 0x21, 0xba, 0x58, 0x4a,
+  0x99, 0xa7, 0x65, 0xe5, 0x85, 0x24, 0x3f, 0x23, 0x42, 0xe7, 0xcb, 0x96,
+  0xa5, 0x03, 0x97, 0x3f, 0xab, 0xea, 0x40, 0x57, 0x6e, 0x1f, 0xa7, 0x4e,
+  0x24, 0x25, 0x4e, 0xee, 0x92, 0x8b, 0xf1, 0xa5, 0xe4, 0x8f, 0xdc, 0x96,
+  0x56, 0x54, 0x01, 0x17, 0x8c, 0x42, 0x07, 0xdf, 0xde, 0xd3, 0xc2, 0xc2,
+  0x7e, 0x97, 0x5d, 0x9d, 0x25, 0xf3, 0xe5, 0x04, 0xee, 0x6e, 0x3b, 0x4f,
+  0x23, 0x25, 0xce, 0x4c, 0x75, 0xe0, 0xe4, 0xe9, 0x19, 0xab, 0x55, 0x2e,
+  0xad, 0x8d, 0xcb, 0xb9, 0x8b, 0xc2, 0x25, 0x91, 0x4e, 0x09, 0x78, 0xaf,
+  0x35, 0x3c, 0x5f, 0xb1, 0xa7, 0x40, 0x9d, 0xe1, 0x12, 0x0b, 0x83, 0xe0,
+  0x7a, 0xc8, 0xf4, 0xaf, 0x81, 0xa6, 0x3c, 0x43, 0xb0, 0xe6, 0x59, 0x9b,
+  0xcc, 0x66, 0x27, 0x75, 0x49, 0x6e, 0x39, 0xef, 0x8b, 0x22, 0xed, 0x9b,
+  0x99, 0xa4, 0x94, 0x94, 0x8e, 0x09, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x07, 0x30, 0x82, 0x01, 0x03, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x30, 0x9a, 0x00, 0x57, 0x99, 0x44,
+  0x63, 0x6b, 0xc9, 0xb2, 0xf2, 0x3d, 0x8d, 0x83, 0x6b, 0x3b, 0xd7, 0x9d,
+  0xef, 0x64, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+  0x16, 0x80, 0x14, 0xa0, 0x73, 0x49, 0x99, 0x68, 0xdc, 0x85, 0x5b, 0x65,
+  0xe3, 0x9b, 0x28, 0x2f, 0x57, 0x9f, 0xbd, 0x33, 0xbc, 0x07, 0x48, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e,
+  0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
+  0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x2f,
+  0x53, 0x43, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x43, 0x52, 0x4c, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, 0x30,
+  0x49, 0x30, 0x47, 0x06, 0x0a, 0x2a, 0x83, 0x08, 0x8c, 0x9b, 0x1b, 0x64,
+  0x87, 0x05, 0x01, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+  0x79, 0x2e, 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74,
+  0x31, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4d, 0xb3,
+  0x4b, 0x19, 0xc4, 0xde, 0xf8, 0x9e, 0x7b, 0x54, 0x3a, 0x69, 0x77, 0xb8,
+  0x31, 0xed, 0x50, 0xdc, 0xf4, 0xd2, 0xad, 0x2f, 0x08, 0x5b, 0xaf, 0x77,
+  0xc1, 0x23, 0xbe, 0x14, 0x38, 0x39, 0xfd, 0x2d, 0x4e, 0xd1, 0xd2, 0x29,
+  0xb6, 0x9d, 0xad, 0x8e, 0x79, 0x76, 0x41, 0x1a, 0xd5, 0x4c, 0xdd, 0xb6,
+  0xd1, 0x54, 0x68, 0xbb, 0x8c, 0x2d, 0xb1, 0xd9, 0x18, 0x77, 0x03, 0xbb,
+  0x46, 0x91, 0x1a, 0x28, 0xec, 0xe1, 0xb8, 0x00, 0x2d, 0x8c, 0xb5, 0x0c,
+  0xa2, 0x24, 0xdf, 0x75, 0xba, 0x58, 0xfc, 0xdd, 0x8a, 0xc1, 0xb4, 0x53,
+  0x12, 0x29, 0x43, 0x77, 0xed, 0x4c, 0x73, 0x8d, 0xb1, 0x49, 0x1f, 0x14,
+  0x56, 0xbe, 0x11, 0x8d, 0x6d, 0xd6, 0x55, 0x35, 0x55, 0x25, 0x48, 0x8a,
+  0x84, 0xe8, 0x3e, 0x74, 0x38, 0xe2, 0x7d, 0xba, 0x37, 0x64, 0x69, 0x14,
+  0xa7, 0x69, 0xd9, 0xc6, 0x40, 0x3e, 0x74, 0xb4, 0x23, 0xbb, 0xc2, 0x71,
+  0x9f, 0x2b, 0x3d, 0x6a, 0x88, 0x81, 0x11, 0xce, 0x2e, 0xbc, 0x12, 0x9f,
+  0xc8, 0x18, 0xe7, 0xf9, 0x63, 0x0d, 0x25, 0x87, 0xc3, 0x85, 0x05, 0xba,
+  0x9c, 0x9d, 0xb7, 0xa7, 0x69, 0x41, 0xee, 0xc8, 0x67, 0x82, 0xd3, 0x2f,
+  0x03, 0xe6, 0x43, 0xc3, 0x53, 0x8a, 0x60, 0x58, 0xd6, 0x2a, 0x1a, 0xc0,
+  0x80, 0x9a, 0x97, 0x89, 0x54, 0x40, 0xe6, 0x25, 0xc5, 0x1e, 0x8e, 0xaf,
+  0x7f, 0xa1, 0x10, 0xbf, 0x37, 0x13, 0x05, 0x1d, 0x8a, 0xd0, 0x42, 0x18,
+  0xf8, 0xbb, 0xf9, 0x64, 0xa8, 0x05, 0x56, 0x06, 0xfa, 0x27, 0x71, 0x0f,
+  0x5c, 0x79, 0x90, 0xff, 0x5a, 0x43, 0xa2, 0xa7, 0xb7, 0x6c, 0x68, 0x64,
+  0x8a, 0x94, 0x25, 0xee, 0xbe, 0x7f, 0x7b, 0x27, 0x0c, 0x92, 0x4b, 0x99,
+  0xc5, 0x33, 0x3d, 0x93, 0xe0, 0x62, 0x71, 0x29, 0x81, 0xcb, 0x26, 0x7a,
+  0xa7, 0xc8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 314159330 (0x12b9b0e2)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1
+        Validity
+            Not Before: Feb 17 04:49:28 2012 GMT
+            Not After : Feb 17 04:49:28 2022 GMT
+        Subject: C=JP, O=SECOM Trust Systems CO.,LTD., CN=SECOM Passport for Web SR 2.0 CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ad:81:dd:77:fa:50:b2:3f:38:54:f7:2b:6a:04:
+                    23:1d:db:84:08:6d:dc:51:41:bf:09:01:d7:84:af:
+                    c8:7e:92:f9:72:47:35:6a:d5:85:eb:2d:5a:82:72:
+                    6f:6c:10:e6:37:9d:fc:d7:0a:5f:99:07:24:3d:50:
+                    bb:36:f7:d1:50:2d:38:5d:bf:6b:56:2e:e6:79:b4:
+                    59:3e:a0:09:7e:d0:8e:a8:0c:ed:0a:c3:5b:f8:79:
+                    b5:c1:23:5a:b3:b5:f3:f2:f6:e4:e4:2a:a9:ff:86:
+                    53:3e:57:cc:9d:00:36:57:f1:c2:af:34:2b:52:68:
+                    57:4d:a1:57:cb:28:c6:21:ba:58:4a:99:a7:65:e5:
+                    85:24:3f:23:42:e7:cb:96:a5:03:97:3f:ab:ea:40:
+                    57:6e:1f:a7:4e:24:25:4e:ee:92:8b:f1:a5:e4:8f:
+                    dc:96:56:54:01:17:8c:42:07:df:de:d3:c2:c2:7e:
+                    97:5d:9d:25:f3:e5:04:ee:6e:3b:4f:23:25:ce:4c:
+                    75:e0:e4:e9:19:ab:55:2e:ad:8d:cb:b9:8b:c2:25:
+                    91:4e:09:78:af:35:3c:5f:b1:a7:40:9d:e1:12:0b:
+                    83:e0:7a:c8:f4:af:81:a6:3c:43:b0:e6:59:9b:cc:
+                    66:27:75:49:6e:39:ef:8b:22:ed:9b:99:a4:94:94:
+                    8e:09
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                30:9A:00:57:99:44:63:6B:C9:B2:F2:3D:8D:83:6B:3B:D7:9D:EF:64
+            X509v3 Authority Key Identifier: 
+                keyid:A0:73:49:99:68:DC:85:5B:65:E3:9B:28:2F:57:9F:BD:33:BC:07:48
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://repository.secomtrust.net/SC-Root1/SCRoot1CRL.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 1.2.392.200091.100.901.1
+                  CPS: https://repository.secomtrust.net/SC-Root1/
+
+    Signature Algorithm: sha1WithRSAEncryption
+         32:ff:99:55:69:c1:3e:48:62:52:69:ac:09:10:5f:93:3f:69:
+         c4:53:47:34:44:a3:7d:9c:19:2f:94:9b:ba:68:14:e7:75:e4:
+         9b:ad:f2:65:a7:68:13:f9:ca:1d:6b:8e:1c:94:62:10:70:4c:
+         14:6a:c5:a9:64:47:c5:c4:de:b3:0d:04:1f:fd:a0:ae:3f:68:
+         59:de:af:4a:b9:d2:cb:8f:09:ec:df:8c:b9:34:3a:0f:5e:e6:
+         bd:97:79:f3:e2:ef:19:6a:8a:97:19:7d:d1:f9:04:41:fc:93:
+         7a:54:44:42:a7:bc:e7:79:a9:9e:68:d8:1c:5d:39:5d:97:bb:
+         bb:f1:14:b7:b5:0c:f3:bc:11:9e:dc:67:b1:df:d4:91:68:e5:
+         df:6d:ac:69:f3:b9:56:87:25:e4:cc:8d:86:d1:38:ce:93:ec:
+         76:c1:c4:01:44:1e:13:16:90:73:5e:5d:80:4d:42:69:68:5b:
+         aa:4d:0f:f5:38:2e:0e:d6:28:9d:d4:b9:5a:d1:4d:08:39:b6:
+         f4:47:9c:82:48:e6:b4:71:8e:0d:05:cd:15:48:8b:e7:ae:12:
+         fd:24:e8:6d:7a:cf:bf:1e:4e:7e:6d:87:f3:af:56:8f:4e:12:
+         42:4f:b4:7c:5b:fd:10:f8:78:e3:9b:52:3d:cd:a9:99:75:b0:
+         c3:c9:fd:24
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIEErmw4jANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK
+UDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBD
+b21tdW5pY2F0aW9uIFJvb3RDQTEwHhcNMTIwMjE3MDQ0OTI4WhcNMjIwMjE3MDQ0
+OTI4WjBfMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVt
+cyBDTy4sTFRELjEpMCcGA1UEAxMgU0VDT00gUGFzc3BvcnQgZm9yIFdlYiBTUiAy
+LjAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtgd13+lCyPzhU
+9ytqBCMd24QIbdxRQb8JAdeEr8h+kvlyRzVq1YXrLVqCcm9sEOY3nfzXCl+ZByQ9
+ULs299FQLThdv2tWLuZ5tFk+oAl+0I6oDO0Kw1v4ebXBI1qztfPy9uTkKqn/hlM+
+V8ydADZX8cKvNCtSaFdNoVfLKMYhulhKmadl5YUkPyNC58uWpQOXP6vqQFduH6dO
+JCVO7pKL8aXkj9yWVlQBF4xCB9/e08LCfpddnSXz5QTubjtPIyXOTHXg5OkZq1Uu
+rY3LuYvCJZFOCXivNTxfsadAneESC4Pgesj0r4GmPEOw5lmbzGYndUluOe+LIu2b
+maSUlI4JAgMBAAGjggEHMIIBAzAdBgNVHQ4EFgQUMJoAV5lEY2vJsvI9jYNrO9ed
+72QwHwYDVR0jBBgwFoAUoHNJmWjchVtl45soL1efvTO8B0gwEgYDVR0TAQH/BAgw
+BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDov
+L3JlcG9zaXRvcnkuc2Vjb210cnVzdC5uZXQvU0MtUm9vdDEvU0NSb290MUNSTC5j
+cmwwUgYDVR0gBEswSTBHBgoqgwiMmxtkhwUBMDkwNwYIKwYBBQUHAgEWK2h0dHBz
+Oi8vcmVwb3NpdG9yeS5zZWNvbXRydXN0Lm5ldC9TQy1Sb290MS8wDQYJKoZIhvcN
+AQEFBQADggEBADL/mVVpwT5IYlJprAkQX5M/acRTRzREo32cGS+Um7poFOd15Jut
+8mWnaBP5yh1rjhyUYhBwTBRqxalkR8XE3rMNBB/9oK4/aFner0q50suPCezfjLk0
+Og9e5r2XefPi7xlqipcZfdH5BEH8k3pUREKnvOd5qZ5o2BxdOV2Xu7vxFLe1DPO8
+EZ7cZ7Hf1JFo5d9trGnzuVaHJeTMjYbROM6T7HbBxAFEHhMWkHNeXYBNQmloW6pN
+D/U4Lg7WKJ3UuVrRTQg5tvRHnIJI5rRxjg0FzRVIi+euEv0k6G16z78eTn5th/Ov
+Vo9OEkJPtHxb/RD4eOObUj3NqZl1sMPJ/SQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert20[] = {
+  0x30, 0x82, 0x04, 0x36, 0x30, 0x82, 0x03, 0x1e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x12, 0xb9, 0xb0, 0xe2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x50,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a,
+  0x50, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x20, 0x43,
+  0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x31, 0x30, 0x1e, 0x17, 0x0d,
+  0x31, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x34, 0x34, 0x39, 0x32, 0x38,
+  0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x34, 0x34,
+  0x39, 0x32, 0x38, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x25, 0x30, 0x23, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+  0x73, 0x20, 0x43, 0x4f, 0x2e, 0x2c, 0x4c, 0x54, 0x44, 0x2e, 0x31, 0x29,
+  0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x45, 0x43,
+  0x4f, 0x4d, 0x20, 0x50, 0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x20,
+  0x66, 0x6f, 0x72, 0x20, 0x57, 0x65, 0x62, 0x20, 0x53, 0x52, 0x20, 0x32,
+  0x2e, 0x30, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xad, 0x81, 0xdd, 0x77, 0xfa, 0x50, 0xb2, 0x3f, 0x38, 0x54,
+  0xf7, 0x2b, 0x6a, 0x04, 0x23, 0x1d, 0xdb, 0x84, 0x08, 0x6d, 0xdc, 0x51,
+  0x41, 0xbf, 0x09, 0x01, 0xd7, 0x84, 0xaf, 0xc8, 0x7e, 0x92, 0xf9, 0x72,
+  0x47, 0x35, 0x6a, 0xd5, 0x85, 0xeb, 0x2d, 0x5a, 0x82, 0x72, 0x6f, 0x6c,
+  0x10, 0xe6, 0x37, 0x9d, 0xfc, 0xd7, 0x0a, 0x5f, 0x99, 0x07, 0x24, 0x3d,
+  0x50, 0xbb, 0x36, 0xf7, 0xd1, 0x50, 0x2d, 0x38, 0x5d, 0xbf, 0x6b, 0x56,
+  0x2e, 0xe6, 0x79, 0xb4, 0x59, 0x3e, 0xa0, 0x09, 0x7e, 0xd0, 0x8e, 0xa8,
+  0x0c, 0xed, 0x0a, 0xc3, 0x5b, 0xf8, 0x79, 0xb5, 0xc1, 0x23, 0x5a, 0xb3,
+  0xb5, 0xf3, 0xf2, 0xf6, 0xe4, 0xe4, 0x2a, 0xa9, 0xff, 0x86, 0x53, 0x3e,
+  0x57, 0xcc, 0x9d, 0x00, 0x36, 0x57, 0xf1, 0xc2, 0xaf, 0x34, 0x2b, 0x52,
+  0x68, 0x57, 0x4d, 0xa1, 0x57, 0xcb, 0x28, 0xc6, 0x21, 0xba, 0x58, 0x4a,
+  0x99, 0xa7, 0x65, 0xe5, 0x85, 0x24, 0x3f, 0x23, 0x42, 0xe7, 0xcb, 0x96,
+  0xa5, 0x03, 0x97, 0x3f, 0xab, 0xea, 0x40, 0x57, 0x6e, 0x1f, 0xa7, 0x4e,
+  0x24, 0x25, 0x4e, 0xee, 0x92, 0x8b, 0xf1, 0xa5, 0xe4, 0x8f, 0xdc, 0x96,
+  0x56, 0x54, 0x01, 0x17, 0x8c, 0x42, 0x07, 0xdf, 0xde, 0xd3, 0xc2, 0xc2,
+  0x7e, 0x97, 0x5d, 0x9d, 0x25, 0xf3, 0xe5, 0x04, 0xee, 0x6e, 0x3b, 0x4f,
+  0x23, 0x25, 0xce, 0x4c, 0x75, 0xe0, 0xe4, 0xe9, 0x19, 0xab, 0x55, 0x2e,
+  0xad, 0x8d, 0xcb, 0xb9, 0x8b, 0xc2, 0x25, 0x91, 0x4e, 0x09, 0x78, 0xaf,
+  0x35, 0x3c, 0x5f, 0xb1, 0xa7, 0x40, 0x9d, 0xe1, 0x12, 0x0b, 0x83, 0xe0,
+  0x7a, 0xc8, 0xf4, 0xaf, 0x81, 0xa6, 0x3c, 0x43, 0xb0, 0xe6, 0x59, 0x9b,
+  0xcc, 0x66, 0x27, 0x75, 0x49, 0x6e, 0x39, 0xef, 0x8b, 0x22, 0xed, 0x9b,
+  0x99, 0xa4, 0x94, 0x94, 0x8e, 0x09, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x07, 0x30, 0x82, 0x01, 0x03, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x30, 0x9a, 0x00, 0x57, 0x99, 0x44,
+  0x63, 0x6b, 0xc9, 0xb2, 0xf2, 0x3d, 0x8d, 0x83, 0x6b, 0x3b, 0xd7, 0x9d,
+  0xef, 0x64, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+  0x16, 0x80, 0x14, 0xa0, 0x73, 0x49, 0x99, 0x68, 0xdc, 0x85, 0x5b, 0x65,
+  0xe3, 0x9b, 0x28, 0x2f, 0x57, 0x9f, 0xbd, 0x33, 0xbc, 0x07, 0x48, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e,
+  0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
+  0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x2f,
+  0x53, 0x43, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x43, 0x52, 0x4c, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, 0x30,
+  0x49, 0x30, 0x47, 0x06, 0x0a, 0x2a, 0x83, 0x08, 0x8c, 0x9b, 0x1b, 0x64,
+  0x87, 0x05, 0x01, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+  0x79, 0x2e, 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74,
+  0x31, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x32, 0xff,
+  0x99, 0x55, 0x69, 0xc1, 0x3e, 0x48, 0x62, 0x52, 0x69, 0xac, 0x09, 0x10,
+  0x5f, 0x93, 0x3f, 0x69, 0xc4, 0x53, 0x47, 0x34, 0x44, 0xa3, 0x7d, 0x9c,
+  0x19, 0x2f, 0x94, 0x9b, 0xba, 0x68, 0x14, 0xe7, 0x75, 0xe4, 0x9b, 0xad,
+  0xf2, 0x65, 0xa7, 0x68, 0x13, 0xf9, 0xca, 0x1d, 0x6b, 0x8e, 0x1c, 0x94,
+  0x62, 0x10, 0x70, 0x4c, 0x14, 0x6a, 0xc5, 0xa9, 0x64, 0x47, 0xc5, 0xc4,
+  0xde, 0xb3, 0x0d, 0x04, 0x1f, 0xfd, 0xa0, 0xae, 0x3f, 0x68, 0x59, 0xde,
+  0xaf, 0x4a, 0xb9, 0xd2, 0xcb, 0x8f, 0x09, 0xec, 0xdf, 0x8c, 0xb9, 0x34,
+  0x3a, 0x0f, 0x5e, 0xe6, 0xbd, 0x97, 0x79, 0xf3, 0xe2, 0xef, 0x19, 0x6a,
+  0x8a, 0x97, 0x19, 0x7d, 0xd1, 0xf9, 0x04, 0x41, 0xfc, 0x93, 0x7a, 0x54,
+  0x44, 0x42, 0xa7, 0xbc, 0xe7, 0x79, 0xa9, 0x9e, 0x68, 0xd8, 0x1c, 0x5d,
+  0x39, 0x5d, 0x97, 0xbb, 0xbb, 0xf1, 0x14, 0xb7, 0xb5, 0x0c, 0xf3, 0xbc,
+  0x11, 0x9e, 0xdc, 0x67, 0xb1, 0xdf, 0xd4, 0x91, 0x68, 0xe5, 0xdf, 0x6d,
+  0xac, 0x69, 0xf3, 0xb9, 0x56, 0x87, 0x25, 0xe4, 0xcc, 0x8d, 0x86, 0xd1,
+  0x38, 0xce, 0x93, 0xec, 0x76, 0xc1, 0xc4, 0x01, 0x44, 0x1e, 0x13, 0x16,
+  0x90, 0x73, 0x5e, 0x5d, 0x80, 0x4d, 0x42, 0x69, 0x68, 0x5b, 0xaa, 0x4d,
+  0x0f, 0xf5, 0x38, 0x2e, 0x0e, 0xd6, 0x28, 0x9d, 0xd4, 0xb9, 0x5a, 0xd1,
+  0x4d, 0x08, 0x39, 0xb6, 0xf4, 0x47, 0x9c, 0x82, 0x48, 0xe6, 0xb4, 0x71,
+  0x8e, 0x0d, 0x05, 0xcd, 0x15, 0x48, 0x8b, 0xe7, 0xae, 0x12, 0xfd, 0x24,
+  0xe8, 0x6d, 0x7a, 0xcf, 0xbf, 0x1e, 0x4e, 0x7e, 0x6d, 0x87, 0xf3, 0xaf,
+  0x56, 0x8f, 0x4e, 0x12, 0x42, 0x4f, 0xb4, 0x7c, 0x5b, 0xfd, 0x10, 0xf8,
+  0x78, 0xe3, 0x9b, 0x52, 0x3d, 0xcd, 0xa9, 0x99, 0x75, 0xb0, 0xc3, 0xc9,
+  0xfd, 0x24,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120024505 (0x7276db9)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Nov 30 16:35:21 2010 GMT
+            Not After : Aug 10 15:34:26 2018 GMT
+        Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+                    d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+                    64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+                    62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+                    52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+                    73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+                    50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+                    a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+                    70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+                    d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+                    5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+                    98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+                    ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+                    39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+                    c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+                    ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+                    78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+                    1a:39
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:3
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+    Signature Algorithm: sha1WithRSAEncryption
+         16:b4:2c:c9:f1:5e:e1:a2:7b:9b:78:20:7a:4a:70:70:86:19:
+         00:b7:05:2a:e8:c9:25:39:0f:c3:64:3c:75:09:d9:89:15:80:
+         07:c2:8d:bc:29:a5:64:50:cf:71:75:47:23:bd:4d:d8:7f:77:
+         9a:51:10:6e:4e:1f:20:3c:47:9c:43:74:7f:96:84:10:4c:13:
+         43:be:f8:e0:72:2e:ff:bf:ae:3c:0a:03:60:82:4b:6f:f9:9a:
+         c5:1e:f6:af:90:3b:9f:61:3b:3e:de:9b:05:1a:c6:2c:3c:57:
+         21:08:0f:54:fa:28:63:6c:e8:1b:9c:0f:cf:dd:30:44:13:b9:
+         57:fe
+-----BEGIN CERTIFICATE-----
+MIIEODCCA6GgAwIBAgIEBydtuTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMTEzMDE2MzUyMVoXDTE4MDgxMDE1MzQyNlowWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAWow
+ggFmMBIGA1UdEwEB/wQIMAYBAf8CAQMwTgYDVR0gBEcwRTBDBgRVHSAAMDswOQYI
+KwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5LmNmbTAOBgNVHQ8BAf8EBAMCAQYwgYkGA1UdIwSBgTB/oXmkdzB1MQswCQYD
+VQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUg
+Q3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRy
+dXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vd3d3
+LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3JsMB0GA1Ud
+DgQWBBTlnVkwgkdYzKz6CFQ2hns6tQRN8DANBgkqhkiG9w0BAQUFAAOBgQAWtCzJ
+8V7honubeCB6SnBwhhkAtwUq6MklOQ/DZDx1CdmJFYAHwo28KaVkUM9xdUcjvU3Y
+f3eaURBuTh8gPEecQ3R/loQQTBNDvvjgci7/v648CgNggktv+ZrFHvavkDufYTs+
+3psFGsYsPFchCA9U+ihjbOgbnA/P3TBEE7lX/g==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert21[] = {
+  0x30, 0x82, 0x04, 0x38, 0x30, 0x82, 0x03, 0xa1, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x6d, 0xb9, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x30, 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x33, 0x35, 0x32,
+  0x31, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x30, 0x31, 0x35,
+  0x33, 0x34, 0x32, 0x36, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69,
+  0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79,
+  0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f,
+  0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f,
+  0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04,
+  0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79,
+  0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e,
+  0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d,
+  0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12,
+  0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88,
+  0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7,
+  0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5,
+  0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab,
+  0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5,
+  0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f,
+  0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77,
+  0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0,
+  0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd,
+  0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0,
+  0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4,
+  0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9,
+  0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c,
+  0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c,
+  0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b,
+  0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76,
+  0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27,
+  0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6a, 0x30,
+  0x82, 0x01, 0x66, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30,
+  0x4e, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+  0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f,
+  0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f,
+  0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30,
+  0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20,
+  0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53,
+  0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36,
+  0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69,
+  0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63,
+  0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58,
+  0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d,
+  0xf0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x16, 0xb4, 0x2c, 0xc9,
+  0xf1, 0x5e, 0xe1, 0xa2, 0x7b, 0x9b, 0x78, 0x20, 0x7a, 0x4a, 0x70, 0x70,
+  0x86, 0x19, 0x00, 0xb7, 0x05, 0x2a, 0xe8, 0xc9, 0x25, 0x39, 0x0f, 0xc3,
+  0x64, 0x3c, 0x75, 0x09, 0xd9, 0x89, 0x15, 0x80, 0x07, 0xc2, 0x8d, 0xbc,
+  0x29, 0xa5, 0x64, 0x50, 0xcf, 0x71, 0x75, 0x47, 0x23, 0xbd, 0x4d, 0xd8,
+  0x7f, 0x77, 0x9a, 0x51, 0x10, 0x6e, 0x4e, 0x1f, 0x20, 0x3c, 0x47, 0x9c,
+  0x43, 0x74, 0x7f, 0x96, 0x84, 0x10, 0x4c, 0x13, 0x43, 0xbe, 0xf8, 0xe0,
+  0x72, 0x2e, 0xff, 0xbf, 0xae, 0x3c, 0x0a, 0x03, 0x60, 0x82, 0x4b, 0x6f,
+  0xf9, 0x9a, 0xc5, 0x1e, 0xf6, 0xaf, 0x90, 0x3b, 0x9f, 0x61, 0x3b, 0x3e,
+  0xde, 0x9b, 0x05, 0x1a, 0xc6, 0x2c, 0x3c, 0x57, 0x21, 0x08, 0x0f, 0x54,
+  0xfa, 0x28, 0x63, 0x6c, 0xe8, 0x1b, 0x9c, 0x0f, 0xcf, 0xdd, 0x30, 0x44,
+  0x13, 0xb9, 0x57, 0xfe,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            48:4b:ac:f1:aa:c7:d7:13:43:d1:a2:74:35:49:97:25
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Jun  7 08:09:10 2005 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:f7:c3:38:3f:b4:a8:7f:cf:39:82:51:67:d0:
+                    6d:9f:d2:ff:58:f3:e7:9f:2b:ec:0d:89:54:99:b9:
+                    38:99:16:f7:e0:21:79:48:c2:bb:61:74:12:96:1d:
+                    3c:6a:72:d5:3c:10:67:3a:39:ed:2b:13:cd:66:eb:
+                    95:09:33:a4:6c:97:b1:e8:c6:ec:c1:75:79:9c:46:
+                    5e:8d:ab:d0:6a:fd:b9:2a:55:17:10:54:b3:19:f0:
+                    9a:f6:f1:b1:5d:b6:a7:6d:fb:e0:71:17:6b:a2:88:
+                    fb:00:df:fe:1a:31:77:0c:9a:01:7a:b1:32:e3:2b:
+                    01:07:38:6e:c3:a5:5e:23:bc:45:9b:7b:50:c1:c9:
+                    30:8f:db:e5:2b:7a:d3:5b:fb:33:40:1e:a0:d5:98:
+                    17:bc:8b:87:c3:89:d3:5d:a0:8e:b2:aa:aa:f6:8e:
+                    69:88:06:c5:fa:89:21:f3:08:9d:69:2e:09:33:9b:
+                    29:0d:46:0f:8c:cc:49:34:b0:69:51:bd:f9:06:cd:
+                    68:ad:66:4c:bc:3e:ac:61:bd:0a:88:0e:c8:df:3d:
+                    ee:7c:04:4c:9d:0a:5e:6b:91:d6:ee:c7:ed:28:8d:
+                    ab:4d:87:89:73:d0:6e:a4:d0:1e:16:8b:14:e1:76:
+                    44:03:7f:63:ac:e4:cd:49:9c:c5:92:f4:ab:32:a1:
+                    48:5b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3c:ec:7b:e0:ae:a3:0e:96:6d:30:d7:85:c6:d2:68:5b:45:5a:
+         82:a6:34:0f:b0:c9:92:23:5e:11:6d:08:11:b2:74:09:23:3a:
+         35:25:73:58:5e:ca:b9:7c:28:fa:47:ec:f9:a0:03:58:50:b6:
+         53:ef:8c:db:39:e4:67:e9:d8:ca:28:46:d4:a7:e0:f5:38:75:
+         f8:e7:cb:5c:bf:1d:11:3c:6a:40:9b:2d:44:56:d3:f7:ff:05:
+         28:32:0c:15:c8:64:45:93:e8:21:24:8f:2d:da:7a:84:7b:4f:
+         cf:cd:b2:25:7c:77:10:d3:94:d1:04:91:a8:25:1c:09:22:0f:
+         7d:44:35:11:14:ef:af:00:fe:5e:ea:5f:8e:b0:d9:92:59:ba:
+         fc:13:96:a0:18:01:56:ce:da:f6:28:0b:b1:af:dd:5c:4f:5c:
+         b2:f3:8f:5a:71:cf:ed:18:ad:63:88:1d:8e:95:f7:ea:95:e7:
+         1f:ad:90:b8:84:08:47:85:7f:22:2f:1a:1d:48:30:d6:4c:08:
+         d8:37:19:67:32:2b:eb:5c:d0:b2:fc:6e:57:9f:04:35:5e:90:
+         00:7e:11:c7:de:13:2a:cd:a4:6d:45:26:c7:88:56:a0:f0:6a:
+         f7:d8:e7:fc:27:7e:67:08:d0:bd:fa:b6:c3:61:02:01:65:b9:
+         b8:2f:cf:5a
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIQSEus8arH1xND0aJ0NUmXJTANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0
+LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0
+qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt
+KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr
+ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX
+vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM
+vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN
+SZzFkvSrMqFIWwIDAQABo4GqMIGnMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D
+veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8v
+Y3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5jcmwwDQYJ
+KoZIhvcNAQEFBQADggEBADzse+Cuow6WbTDXhcbSaFtFWoKmNA+wyZIjXhFtCBGy
+dAkjOjUlc1heyrl8KPpH7PmgA1hQtlPvjNs55Gfp2MooRtSn4PU4dfjny1y/HRE8
+akCbLURW0/f/BSgyDBXIZEWT6CEkjy3aeoR7T8/NsiV8dxDTlNEEkaglHAkiD31E
+NREU768A/l7qX46w2ZJZuvwTlqAYAVbO2vYoC7Gv3VxPXLLzj1pxz+0YrWOIHY6V
+9+qV5x+tkLiECEeFfyIvGh1IMNZMCNg3GWcyK+tc0LL8blefBDVekAB+EcfeEyrN
+pG1FJseIVqDwavfY5/wnfmcI0L36tsNhAgFlubgvz1o=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert22[] = {
+  0x30, 0x82, 0x04, 0x3c, 0x30, 0x82, 0x03, 0x24, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x48, 0x4b, 0xac, 0xf1, 0xaa, 0xc7, 0xd7, 0x13, 0x43,
+  0xd1, 0xa2, 0x74, 0x35, 0x49, 0x97, 0x25, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+  0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+  0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+  0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55,
+  0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+  0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xf7, 0xc3, 0x38, 0x3f, 0xb4,
+  0xa8, 0x7f, 0xcf, 0x39, 0x82, 0x51, 0x67, 0xd0, 0x6d, 0x9f, 0xd2, 0xff,
+  0x58, 0xf3, 0xe7, 0x9f, 0x2b, 0xec, 0x0d, 0x89, 0x54, 0x99, 0xb9, 0x38,
+  0x99, 0x16, 0xf7, 0xe0, 0x21, 0x79, 0x48, 0xc2, 0xbb, 0x61, 0x74, 0x12,
+  0x96, 0x1d, 0x3c, 0x6a, 0x72, 0xd5, 0x3c, 0x10, 0x67, 0x3a, 0x39, 0xed,
+  0x2b, 0x13, 0xcd, 0x66, 0xeb, 0x95, 0x09, 0x33, 0xa4, 0x6c, 0x97, 0xb1,
+  0xe8, 0xc6, 0xec, 0xc1, 0x75, 0x79, 0x9c, 0x46, 0x5e, 0x8d, 0xab, 0xd0,
+  0x6a, 0xfd, 0xb9, 0x2a, 0x55, 0x17, 0x10, 0x54, 0xb3, 0x19, 0xf0, 0x9a,
+  0xf6, 0xf1, 0xb1, 0x5d, 0xb6, 0xa7, 0x6d, 0xfb, 0xe0, 0x71, 0x17, 0x6b,
+  0xa2, 0x88, 0xfb, 0x00, 0xdf, 0xfe, 0x1a, 0x31, 0x77, 0x0c, 0x9a, 0x01,
+  0x7a, 0xb1, 0x32, 0xe3, 0x2b, 0x01, 0x07, 0x38, 0x6e, 0xc3, 0xa5, 0x5e,
+  0x23, 0xbc, 0x45, 0x9b, 0x7b, 0x50, 0xc1, 0xc9, 0x30, 0x8f, 0xdb, 0xe5,
+  0x2b, 0x7a, 0xd3, 0x5b, 0xfb, 0x33, 0x40, 0x1e, 0xa0, 0xd5, 0x98, 0x17,
+  0xbc, 0x8b, 0x87, 0xc3, 0x89, 0xd3, 0x5d, 0xa0, 0x8e, 0xb2, 0xaa, 0xaa,
+  0xf6, 0x8e, 0x69, 0x88, 0x06, 0xc5, 0xfa, 0x89, 0x21, 0xf3, 0x08, 0x9d,
+  0x69, 0x2e, 0x09, 0x33, 0x9b, 0x29, 0x0d, 0x46, 0x0f, 0x8c, 0xcc, 0x49,
+  0x34, 0xb0, 0x69, 0x51, 0xbd, 0xf9, 0x06, 0xcd, 0x68, 0xad, 0x66, 0x4c,
+  0xbc, 0x3e, 0xac, 0x61, 0xbd, 0x0a, 0x88, 0x0e, 0xc8, 0xdf, 0x3d, 0xee,
+  0x7c, 0x04, 0x4c, 0x9d, 0x0a, 0x5e, 0x6b, 0x91, 0xd6, 0xee, 0xc7, 0xed,
+  0x28, 0x8d, 0xab, 0x4d, 0x87, 0x89, 0x73, 0xd0, 0x6e, 0xa4, 0xd0, 0x1e,
+  0x16, 0x8b, 0x14, 0xe1, 0x76, 0x44, 0x03, 0x7f, 0x63, 0xac, 0xe4, 0xcd,
+  0x49, 0x9c, 0xc5, 0x92, 0xf4, 0xab, 0x32, 0xa1, 0x48, 0x5b, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x81, 0xaa, 0x30, 0x81, 0xa7, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+  0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+  0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+  0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+  0x45, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x44,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+  0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+  0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x3c, 0xec, 0x7b, 0xe0, 0xae, 0xa3, 0x0e, 0x96,
+  0x6d, 0x30, 0xd7, 0x85, 0xc6, 0xd2, 0x68, 0x5b, 0x45, 0x5a, 0x82, 0xa6,
+  0x34, 0x0f, 0xb0, 0xc9, 0x92, 0x23, 0x5e, 0x11, 0x6d, 0x08, 0x11, 0xb2,
+  0x74, 0x09, 0x23, 0x3a, 0x35, 0x25, 0x73, 0x58, 0x5e, 0xca, 0xb9, 0x7c,
+  0x28, 0xfa, 0x47, 0xec, 0xf9, 0xa0, 0x03, 0x58, 0x50, 0xb6, 0x53, 0xef,
+  0x8c, 0xdb, 0x39, 0xe4, 0x67, 0xe9, 0xd8, 0xca, 0x28, 0x46, 0xd4, 0xa7,
+  0xe0, 0xf5, 0x38, 0x75, 0xf8, 0xe7, 0xcb, 0x5c, 0xbf, 0x1d, 0x11, 0x3c,
+  0x6a, 0x40, 0x9b, 0x2d, 0x44, 0x56, 0xd3, 0xf7, 0xff, 0x05, 0x28, 0x32,
+  0x0c, 0x15, 0xc8, 0x64, 0x45, 0x93, 0xe8, 0x21, 0x24, 0x8f, 0x2d, 0xda,
+  0x7a, 0x84, 0x7b, 0x4f, 0xcf, 0xcd, 0xb2, 0x25, 0x7c, 0x77, 0x10, 0xd3,
+  0x94, 0xd1, 0x04, 0x91, 0xa8, 0x25, 0x1c, 0x09, 0x22, 0x0f, 0x7d, 0x44,
+  0x35, 0x11, 0x14, 0xef, 0xaf, 0x00, 0xfe, 0x5e, 0xea, 0x5f, 0x8e, 0xb0,
+  0xd9, 0x92, 0x59, 0xba, 0xfc, 0x13, 0x96, 0xa0, 0x18, 0x01, 0x56, 0xce,
+  0xda, 0xf6, 0x28, 0x0b, 0xb1, 0xaf, 0xdd, 0x5c, 0x4f, 0x5c, 0xb2, 0xf3,
+  0x8f, 0x5a, 0x71, 0xcf, 0xed, 0x18, 0xad, 0x63, 0x88, 0x1d, 0x8e, 0x95,
+  0xf7, 0xea, 0x95, 0xe7, 0x1f, 0xad, 0x90, 0xb8, 0x84, 0x08, 0x47, 0x85,
+  0x7f, 0x22, 0x2f, 0x1a, 0x1d, 0x48, 0x30, 0xd6, 0x4c, 0x08, 0xd8, 0x37,
+  0x19, 0x67, 0x32, 0x2b, 0xeb, 0x5c, 0xd0, 0xb2, 0xfc, 0x6e, 0x57, 0x9f,
+  0x04, 0x35, 0x5e, 0x90, 0x00, 0x7e, 0x11, 0xc7, 0xde, 0x13, 0x2a, 0xcd,
+  0xa4, 0x6d, 0x45, 0x26, 0xc7, 0x88, 0x56, 0xa0, 0xf0, 0x6a, 0xf7, 0xd8,
+  0xe7, 0xfc, 0x27, 0x7e, 0x67, 0x08, 0xd0, 0xbd, 0xfa, 0xb6, 0xc3, 0x61,
+  0x02, 0x01, 0x65, 0xb9, 0xb8, 0x2f, 0xcf, 0x5a,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120001525 (0x72713f5)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Jan 17 15:16:20 2007 GMT
+            Not After : Jan 17 15:15:46 2014 GMT
+        Subject: DC=ru, DC=yandex, DC=ld, CN=YandexExternalCA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ce:7f:78:9c:38:3c:99:97:b3:5c:22:91:ca:b8:
+                    94:39:a2:5e:57:fe:ba:15:1c:98:86:d2:cd:b0:bb:
+                    7b:d7:62:6c:84:61:31:50:2c:63:35:aa:bf:f2:8c:
+                    e0:0a:9c:db:70:a5:03:2b:f7:cf:aa:b8:ee:d2:5a:
+                    cf:13:be:dc:53:fa:67:fb:e6:5a:6d:46:e0:f6:25:
+                    ac:03:d9:5a:e4:aa:af:e0:bf:dd:8b:d2:5c:a0:ea:
+                    f7:e6:5a:0a:2f:5a:11:9f:b4:a8:f2:e9:2f:0b:3d:
+                    31:a1:b3:2a:5f:3c:4b:c2:8c:1c:c6:dc:87:32:22:
+                    55:0f:4b:fe:15:22:f9:39:85:72:cd:16:5b:d1:f6:
+                    23:e3:31:9e:8f:7e:cd:4c:7d:4f:86:c2:e7:41:5a:
+                    41:b8:1d:e7:d2:4d:ca:ec:25:5e:23:fe:5f:de:39:
+                    12:24:09:cd:fa:c9:65:93:26:b0:94:4d:38:a0:c7:
+                    9d:2a:79:18:e2:1f:a0:2a:f1:4c:44:85:a3:4d:53:
+                    a1:91:3a:01:10:c9:aa:c3:4f:49:fb:f1:9b:b8:bf:
+                    cf:d2:e9:b4:41:84:bf:aa:c8:33:13:50:3b:97:cc:
+                    bb:1e:0c:da:f9:8b:5c:3c:83:a3:59:f5:76:ef:98:
+                    c1:78:7e:5e:52:18:02:8a:36:d2:c5:c5:f7:83:aa:
+                    ca:17
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://www.public-trust.com/CPS/OmniRoot.html
+
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                DB:41:27:30:4F:1A:F5:5B:3E:84:56:C8:EC:85:98:B3:51:2C:2D:27
+    Signature Algorithm: sha1WithRSAEncryption
+         19:b8:d2:c4:39:b0:e5:1d:d5:b7:40:96:e8:92:ae:40:36:b4:
+         e9:f7:f5:8b:2d:d4:4e:36:31:4a:d2:d3:e4:1e:ae:45:8d:ec:
+         97:e0:68:0f:56:f0:14:4e:e4:1a:c9:d0:b7:e6:7c:fb:1f:ed:
+         52:19:90:69:f4:5f:a9:4f:d6:27:68:d1:fa:94:a9:7b:a3:c9:
+         97:3c:e0:b3:9d:06:1e:22:f1:82:80:8e:0b:d6:eb:f7:ed:0b:
+         41:bd:ba:e2:07:f2:3c:87:e1:58:ff:8d:c5:32:30:27:93:d7:
+         22:47:5c:60:6c:04:4a:e1:b5:0a:65:a3:dd:f4:c7:54:fb:f4:
+         d8:ef
+-----BEGIN CERTIFICATE-----
+MIIEPjCCA6egAwIBAgIEBycT9TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA3MDExNzE1MTYyMFoXDTE0MDExNzE1MTU0NlowWzESMBAG
+CgmSJomT8ixkARkWAnJ1MRYwFAYKCZImiZPyLGQBGRYGeWFuZGV4MRIwEAYKCZIm
+iZPyLGQBGRYCbGQxGTAXBgNVBAMTEFlhbmRleEV4dGVybmFsQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOf3icODyZl7NcIpHKuJQ5ol5X/roVHJiG
+0s2wu3vXYmyEYTFQLGM1qr/yjOAKnNtwpQMr98+quO7SWs8TvtxT+mf75lptRuD2
+JawD2Vrkqq/gv92L0lyg6vfmWgovWhGftKjy6S8LPTGhsypfPEvCjBzG3IcyIlUP
+S/4VIvk5hXLNFlvR9iPjMZ6Pfs1MfU+GwudBWkG4HefSTcrsJV4j/l/eORIkCc36
+yWWTJrCUTTigx50qeRjiH6Aq8UxEhaNNU6GROgEQyarDT0n78Zu4v8/S6bRBhL+q
+yDMTUDuXzLseDNr5i1w8g6NZ9XbvmMF4fl5SGAKKNtLFxfeDqsoXAgMBAAGjggFv
+MIIBazASBgNVHRMBAf8ECDAGAQH/AgEBMFMGA1UdIARMMEowSAYJKwYBBAGxPgEA
+MDswOQYIKwYBBQUHAgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMv
+T21uaVJvb3QuaHRtbDAOBgNVHQ8BAf8EBAMCAYYwgYkGA1UdIwSBgTB/oXmkdzB1
+MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBD
+eWJlclRydXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRw
+Oi8vd3d3LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3Js
+MB0GA1UdDgQWBBTbQScwTxr1Wz6EVsjshZizUSwtJzANBgkqhkiG9w0BAQUFAAOB
+gQAZuNLEObDlHdW3QJbokq5ANrTp9/WLLdRONjFK0tPkHq5FjeyX4GgPVvAUTuQa
+ydC35nz7H+1SGZBp9F+pT9YnaNH6lKl7o8mXPOCznQYeIvGCgI4L1uv37QtBvbri
+B/I8h+FY/43FMjAnk9ciR1xgbARK4bUKZaPd9MdU+/TY7w==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert23[] = {
+  0x30, 0x82, 0x04, 0x3e, 0x30, 0x82, 0x03, 0xa7, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x13, 0xf5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x30, 0x37, 0x30, 0x31, 0x31, 0x37, 0x31, 0x35, 0x31, 0x36, 0x32,
+  0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x31, 0x31, 0x37, 0x31, 0x35,
+  0x31, 0x35, 0x34, 0x36, 0x5a, 0x30, 0x5b, 0x31, 0x12, 0x30, 0x10, 0x06,
+  0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16,
+  0x02, 0x72, 0x75, 0x31, 0x16, 0x30, 0x14, 0x06, 0x0a, 0x09, 0x92, 0x26,
+  0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x06, 0x79, 0x61, 0x6e,
+  0x64, 0x65, 0x78, 0x31, 0x12, 0x30, 0x10, 0x06, 0x0a, 0x09, 0x92, 0x26,
+  0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x02, 0x6c, 0x64, 0x31,
+  0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x59, 0x61,
+  0x6e, 0x64, 0x65, 0x78, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xce,
+  0x7f, 0x78, 0x9c, 0x38, 0x3c, 0x99, 0x97, 0xb3, 0x5c, 0x22, 0x91, 0xca,
+  0xb8, 0x94, 0x39, 0xa2, 0x5e, 0x57, 0xfe, 0xba, 0x15, 0x1c, 0x98, 0x86,
+  0xd2, 0xcd, 0xb0, 0xbb, 0x7b, 0xd7, 0x62, 0x6c, 0x84, 0x61, 0x31, 0x50,
+  0x2c, 0x63, 0x35, 0xaa, 0xbf, 0xf2, 0x8c, 0xe0, 0x0a, 0x9c, 0xdb, 0x70,
+  0xa5, 0x03, 0x2b, 0xf7, 0xcf, 0xaa, 0xb8, 0xee, 0xd2, 0x5a, 0xcf, 0x13,
+  0xbe, 0xdc, 0x53, 0xfa, 0x67, 0xfb, 0xe6, 0x5a, 0x6d, 0x46, 0xe0, 0xf6,
+  0x25, 0xac, 0x03, 0xd9, 0x5a, 0xe4, 0xaa, 0xaf, 0xe0, 0xbf, 0xdd, 0x8b,
+  0xd2, 0x5c, 0xa0, 0xea, 0xf7, 0xe6, 0x5a, 0x0a, 0x2f, 0x5a, 0x11, 0x9f,
+  0xb4, 0xa8, 0xf2, 0xe9, 0x2f, 0x0b, 0x3d, 0x31, 0xa1, 0xb3, 0x2a, 0x5f,
+  0x3c, 0x4b, 0xc2, 0x8c, 0x1c, 0xc6, 0xdc, 0x87, 0x32, 0x22, 0x55, 0x0f,
+  0x4b, 0xfe, 0x15, 0x22, 0xf9, 0x39, 0x85, 0x72, 0xcd, 0x16, 0x5b, 0xd1,
+  0xf6, 0x23, 0xe3, 0x31, 0x9e, 0x8f, 0x7e, 0xcd, 0x4c, 0x7d, 0x4f, 0x86,
+  0xc2, 0xe7, 0x41, 0x5a, 0x41, 0xb8, 0x1d, 0xe7, 0xd2, 0x4d, 0xca, 0xec,
+  0x25, 0x5e, 0x23, 0xfe, 0x5f, 0xde, 0x39, 0x12, 0x24, 0x09, 0xcd, 0xfa,
+  0xc9, 0x65, 0x93, 0x26, 0xb0, 0x94, 0x4d, 0x38, 0xa0, 0xc7, 0x9d, 0x2a,
+  0x79, 0x18, 0xe2, 0x1f, 0xa0, 0x2a, 0xf1, 0x4c, 0x44, 0x85, 0xa3, 0x4d,
+  0x53, 0xa1, 0x91, 0x3a, 0x01, 0x10, 0xc9, 0xaa, 0xc3, 0x4f, 0x49, 0xfb,
+  0xf1, 0x9b, 0xb8, 0xbf, 0xcf, 0xd2, 0xe9, 0xb4, 0x41, 0x84, 0xbf, 0xaa,
+  0xc8, 0x33, 0x13, 0x50, 0x3b, 0x97, 0xcc, 0xbb, 0x1e, 0x0c, 0xda, 0xf9,
+  0x8b, 0x5c, 0x3c, 0x83, 0xa3, 0x59, 0xf5, 0x76, 0xef, 0x98, 0xc1, 0x78,
+  0x7e, 0x5e, 0x52, 0x18, 0x02, 0x8a, 0x36, 0xd2, 0xc5, 0xc5, 0xf7, 0x83,
+  0xaa, 0xca, 0x17, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6f,
+  0x30, 0x82, 0x01, 0x6b, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+  0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30,
+  0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+  0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x2f,
+  0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x68, 0x74, 0x6d,
+  0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01,
+  0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c,
+  0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69,
+  0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f,
+  0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb,
+  0x41, 0x27, 0x30, 0x4f, 0x1a, 0xf5, 0x5b, 0x3e, 0x84, 0x56, 0xc8, 0xec,
+  0x85, 0x98, 0xb3, 0x51, 0x2c, 0x2d, 0x27, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
+  0x81, 0x00, 0x19, 0xb8, 0xd2, 0xc4, 0x39, 0xb0, 0xe5, 0x1d, 0xd5, 0xb7,
+  0x40, 0x96, 0xe8, 0x92, 0xae, 0x40, 0x36, 0xb4, 0xe9, 0xf7, 0xf5, 0x8b,
+  0x2d, 0xd4, 0x4e, 0x36, 0x31, 0x4a, 0xd2, 0xd3, 0xe4, 0x1e, 0xae, 0x45,
+  0x8d, 0xec, 0x97, 0xe0, 0x68, 0x0f, 0x56, 0xf0, 0x14, 0x4e, 0xe4, 0x1a,
+  0xc9, 0xd0, 0xb7, 0xe6, 0x7c, 0xfb, 0x1f, 0xed, 0x52, 0x19, 0x90, 0x69,
+  0xf4, 0x5f, 0xa9, 0x4f, 0xd6, 0x27, 0x68, 0xd1, 0xfa, 0x94, 0xa9, 0x7b,
+  0xa3, 0xc9, 0x97, 0x3c, 0xe0, 0xb3, 0x9d, 0x06, 0x1e, 0x22, 0xf1, 0x82,
+  0x80, 0x8e, 0x0b, 0xd6, 0xeb, 0xf7, 0xed, 0x0b, 0x41, 0xbd, 0xba, 0xe2,
+  0x07, 0xf2, 0x3c, 0x87, 0xe1, 0x58, 0xff, 0x8d, 0xc5, 0x32, 0x30, 0x27,
+  0x93, 0xd7, 0x22, 0x47, 0x5c, 0x60, 0x6c, 0x04, 0x4a, 0xe1, 0xb5, 0x0a,
+  0x65, 0xa3, 0xdd, 0xf4, 0xc7, 0x54, 0xfb, 0xf4, 0xd8, 0xef,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1116160165 (0x428740a5)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: Oct  1 05:00:00 2006 GMT
+            Not After : Jul 26 18:15:15 2014 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+                    e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+                    a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+                    a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+                    cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+                    91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+                    f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+                    55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+                    be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+                    ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+                    22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+                    57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+                    68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+                    64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+                    39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+                    a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+                    87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+                    4b:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, E-mail Protection
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/server1.crl
+
+            X509v3 Subject Key Identifier: 
+                B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+            X509v3 Key Usage: 
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+            1.2.840.113533.7.65.0: 
+                0
+..V7.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         48:0e:2b:6f:20:62:4c:28:93:a3:24:3d:58:ab:21:cf:80:f8:
+         9a:97:90:6a:22:ed:5a:7c:47:36:99:e7:79:84:75:ab:24:8f:
+         92:0a:d5:61:04:ae:c3:6a:5c:b2:cc:d9:e4:44:87:6f:db:8f:
+         38:62:f7:44:36:9d:ba:bc:6e:07:c4:d4:8d:e8:1f:d1:0b:60:
+         a3:b5:9c:ce:63:be:ed:67:dc:f8:ba:de:6e:c9:25:cb:5b:b5:
+         9d:76:70:0b:df:42:72:f8:4f:41:11:64:a5:d2:ea:fc:d5:af:
+         11:f4:15:38:67:9c:20:a8:4b:77:5a:91:32:42:32:e7:85:b3:
+         df:36
+-----BEGIN CERTIFICATE-----
+MIIEQjCCA6ugAwIBAgIEQodApTANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEw
+MDEwNTAwMDBaFw0xNDA3MjYxODE1MTVaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
+EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
+BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD
+1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt
+cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46
+OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd
+HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm
+t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET
+MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr
+BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo
+dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v
+Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU
+mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7
+UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF
+BQADgYEASA4rbyBiTCiToyQ9WKshz4D4mpeQaiLtWnxHNpnneYR1qySPkgrVYQSu
+w2pcsszZ5ESHb9uPOGL3RDadurxuB8TUjegf0Qtgo7WczmO+7Wfc+Lrebskly1u1
+nXZwC99CcvhPQRFkpdLq/NWvEfQVOGecIKhLd1qRMkIy54Wz3zY=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert24[] = {
+  0x30, 0x82, 0x04, 0x42, 0x30, 0x82, 0x03, 0xab, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x42, 0x87, 0x40, 0xa5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+  0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+  0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+  0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+  0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x30,
+  0x30, 0x31, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31,
+  0x34, 0x30, 0x37, 0x32, 0x36, 0x31, 0x38, 0x31, 0x35, 0x31, 0x35, 0x5a,
+  0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49,
+  0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+  0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61,
+  0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6,
+  0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6,
+  0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3,
+  0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69, 0xa6, 0x40,
+  0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, 0x3a,
+  0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d,
+  0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d,
+  0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4, 0x84, 0xf8,
+  0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, 0x06,
+  0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89,
+  0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a,
+  0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24, 0x45, 0x9a,
+  0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, 0x51,
+  0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50,
+  0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d,
+  0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4, 0x68, 0x73,
+  0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, 0xdb,
+  0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29,
+  0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66,
+  0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab, 0x0a, 0x64,
+  0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, 0x5d,
+  0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38,
+  0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x13,
+  0x30, 0x82, 0x01, 0x0f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+  0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x20, 0x30, 0x1e, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65,
+  0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0,
+  0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4,
+  0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0b,
+  0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d, 0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb,
+  0x50, 0x84, 0x97, 0xf3, 0xed, 0x62, 0xd0, 0x1a, 0x30, 0x19, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30,
+  0x0a, 0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x48, 0x0e, 0x2b, 0x6f, 0x20, 0x62,
+  0x4c, 0x28, 0x93, 0xa3, 0x24, 0x3d, 0x58, 0xab, 0x21, 0xcf, 0x80, 0xf8,
+  0x9a, 0x97, 0x90, 0x6a, 0x22, 0xed, 0x5a, 0x7c, 0x47, 0x36, 0x99, 0xe7,
+  0x79, 0x84, 0x75, 0xab, 0x24, 0x8f, 0x92, 0x0a, 0xd5, 0x61, 0x04, 0xae,
+  0xc3, 0x6a, 0x5c, 0xb2, 0xcc, 0xd9, 0xe4, 0x44, 0x87, 0x6f, 0xdb, 0x8f,
+  0x38, 0x62, 0xf7, 0x44, 0x36, 0x9d, 0xba, 0xbc, 0x6e, 0x07, 0xc4, 0xd4,
+  0x8d, 0xe8, 0x1f, 0xd1, 0x0b, 0x60, 0xa3, 0xb5, 0x9c, 0xce, 0x63, 0xbe,
+  0xed, 0x67, 0xdc, 0xf8, 0xba, 0xde, 0x6e, 0xc9, 0x25, 0xcb, 0x5b, 0xb5,
+  0x9d, 0x76, 0x70, 0x0b, 0xdf, 0x42, 0x72, 0xf8, 0x4f, 0x41, 0x11, 0x64,
+  0xa5, 0xd2, 0xea, 0xfc, 0xd5, 0xaf, 0x11, 0xf4, 0x15, 0x38, 0x67, 0x9c,
+  0x20, 0xa8, 0x4b, 0x77, 0x5a, 0x91, 0x32, 0x42, 0x32, 0xe7, 0x85, 0xb3,
+  0xdf, 0x36,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            33:65:50:08:79:ad:73:e2:30:b9:e0:1d:0d:7f:ac:91
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+        Validity
+            Not Before: Nov 17 00:00:00 2006 GMT
+            Not After : Dec 30 23:59:59 2020 GMT
+        Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ac:a0:f0:fb:80:59:d4:9c:c7:a4:cf:9d:a1:59:
+                    73:09:10:45:0c:0d:2c:6e:68:f1:6c:5b:48:68:49:
+                    59:37:fc:0b:33:19:c2:77:7f:cc:10:2d:95:34:1c:
+                    e6:eb:4d:09:a7:1c:d2:b8:c9:97:36:02:b7:89:d4:
+                    24:5f:06:c0:cc:44:94:94:8d:02:62:6f:eb:5a:dd:
+                    11:8d:28:9a:5c:84:90:10:7a:0d:bd:74:66:2f:6a:
+                    38:a0:e2:d5:54:44:eb:1d:07:9f:07:ba:6f:ee:e9:
+                    fd:4e:0b:29:f5:3e:84:a0:01:f1:9c:ab:f8:1c:7e:
+                    89:a4:e8:a1:d8:71:65:0d:a3:51:7b:ee:bc:d2:22:
+                    60:0d:b9:5b:9d:df:ba:fc:51:5b:0b:af:98:b2:e9:
+                    2e:e9:04:e8:62:87:de:2b:c8:d7:4e:c1:4c:64:1e:
+                    dd:cf:87:58:ba:4a:4f:ca:68:07:1d:1c:9d:4a:c6:
+                    d5:2f:91:cc:7c:71:72:1c:c5:c0:67:eb:32:fd:c9:
+                    92:5c:94:da:85:c0:9b:bf:53:7d:2b:09:f4:8c:9d:
+                    91:1f:97:6a:52:cb:de:09:36:a4:77:d8:7b:87:50:
+                    44:d5:3e:6e:29:69:fb:39:49:26:1e:09:a5:80:7b:
+                    40:2d:eb:e8:27:85:c9:fe:61:fd:7e:e6:7c:97:1d:
+                    d5:9d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.thawte.com/cps
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.thawte.com/ThawtePremiumServerCA.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         84:a8:4c:c9:3e:2a:bc:9a:e2:cc:8f:0b:b2:25:77:c4:61:89:
+         89:63:5a:d4:a3:15:40:d4:fb:5e:3f:b4:43:ea:63:17:2b:6b:
+         99:74:9e:09:a8:dd:d4:56:15:2e:7a:79:31:5f:63:96:53:1b:
+         34:d9:15:ea:4f:6d:70:ca:be:f6:82:a9:ed:da:85:77:cc:76:
+         1c:6a:81:0a:21:d8:41:99:7f:5e:2e:82:c1:e8:aa:f7:93:81:
+         05:aa:92:b4:1f:b7:9a:c0:07:17:f5:cb:c6:b4:4c:0e:d7:56:
+         dc:71:20:74:38:d6:74:c6:d6:8f:6b:af:8b:8d:a0:6c:29:0b:
+         61:e0
+-----BEGIN CERTIFICATE-----
+MIIERTCCA66gAwIBAgIQM2VQCHmtc+IwueAdDX+skTANBgkqhkiG9w0BAQUFADCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTA2MTExNzAwMDAwMFoXDTIwMTIzMDIzNTk1OVow
+gakxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xKDAmBgNVBAsT
+H0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xODA2BgNVBAsTLyhjKSAy
+MDA2IHRoYXd0ZSwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQDExZ0aGF3dGUgUHJpbWFyeSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEArKDw+4BZ1JzHpM+doVlzCRBFDA0sbmjxbFtIaElZN/wLMxnC
+d3/MEC2VNBzm600JpxzSuMmXNgK3idQkXwbAzESUlI0CYm/rWt0RjSiaXISQEHoN
+vXRmL2o4oOLVVETrHQefB7pv7un9Tgsp9T6EoAHxnKv4HH6JpOih2HFlDaNRe+68
+0iJgDblbnd+6/FFbC6+Ysuku6QToYofeK8jXTsFMZB7dz4dYukpPymgHHRydSsbV
+L5HMfHFyHMXAZ+sy/cmSXJTahcCbv1N9Kwn0jJ2RH5dqUsveCTakd9h7h1BE1T5u
+KWn7OUkmHgmlgHtALevoJ4XJ/mH9fuZ8lx3VnQIDAQABo4HCMIG/MA8GA1UdEwEB
+/wQFMAMBAf8wOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHBz
+Oi8vd3d3LnRoYXd0ZS5jb20vY3BzMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+e1tFz6/Oy3r9MZIaarbzRutXSFAwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cDovL2Ny
+bC50aGF3dGUuY29tL1RoYXd0ZVByZW1pdW1TZXJ2ZXJDQS5jcmwwDQYJKoZIhvcN
+AQEFBQADgYEAhKhMyT4qvJrizI8LsiV3xGGJiWNa1KMVQNT7Xj+0Q+pjFytrmXSe
+Cajd1FYVLnp5MV9jllMbNNkV6k9tcMq+9oKp7dqFd8x2HGqBCiHYQZl/Xi6Cweiq
+95OBBaqStB+3msAHF/XLxrRMDtdW3HEgdDjWdMbWj2uvi42gbCkLYeA=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert25[] = {
+  0x30, 0x82, 0x04, 0x45, 0x30, 0x82, 0x03, 0xae, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x33, 0x65, 0x50, 0x08, 0x79, 0xad, 0x73, 0xe2, 0x30,
+  0xb9, 0xe0, 0x1d, 0x0d, 0x7f, 0xac, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+  0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+  0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+  0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+  0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+  0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+  0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+  0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+  0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+  0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31,
+  0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30,
+  0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20,
+  0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32,
+  0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+  0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+  0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74,
+  0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d,
+  0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1,
+  0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2,
+  0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09,
+  0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24,
+  0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb,
+  0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d,
+  0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb,
+  0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29,
+  0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89,
+  0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc,
+  0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b,
+  0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde,
+  0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58,
+  0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5,
+  0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32,
+  0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d,
+  0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde,
+  0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e,
+  0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40,
+  0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c,
+  0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xc2,
+  0x30, 0x81, 0xbf, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74,
+  0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a,
+  0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0, 0x33, 0xa0,
+  0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65, 0x6d, 0x69,
+  0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0xa8, 0x4c,
+  0xc9, 0x3e, 0x2a, 0xbc, 0x9a, 0xe2, 0xcc, 0x8f, 0x0b, 0xb2, 0x25, 0x77,
+  0xc4, 0x61, 0x89, 0x89, 0x63, 0x5a, 0xd4, 0xa3, 0x15, 0x40, 0xd4, 0xfb,
+  0x5e, 0x3f, 0xb4, 0x43, 0xea, 0x63, 0x17, 0x2b, 0x6b, 0x99, 0x74, 0x9e,
+  0x09, 0xa8, 0xdd, 0xd4, 0x56, 0x15, 0x2e, 0x7a, 0x79, 0x31, 0x5f, 0x63,
+  0x96, 0x53, 0x1b, 0x34, 0xd9, 0x15, 0xea, 0x4f, 0x6d, 0x70, 0xca, 0xbe,
+  0xf6, 0x82, 0xa9, 0xed, 0xda, 0x85, 0x77, 0xcc, 0x76, 0x1c, 0x6a, 0x81,
+  0x0a, 0x21, 0xd8, 0x41, 0x99, 0x7f, 0x5e, 0x2e, 0x82, 0xc1, 0xe8, 0xaa,
+  0xf7, 0x93, 0x81, 0x05, 0xaa, 0x92, 0xb4, 0x1f, 0xb7, 0x9a, 0xc0, 0x07,
+  0x17, 0xf5, 0xcb, 0xc6, 0xb4, 0x4c, 0x0e, 0xd7, 0x56, 0xdc, 0x71, 0x20,
+  0x74, 0x38, 0xd6, 0x74, 0xc6, 0xd6, 0x8f, 0x6b, 0xaf, 0x8b, 0x8d, 0xa0,
+  0x6c, 0x29, 0x0b, 0x61, 0xe0,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120026506 (0x727758a)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Validity
+            Not Before: Jul 25 17:58:28 2012 GMT
+            Not After : Jul 25 17:57:44 2019 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+                    e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+                    a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+                    a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+                    cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+                    91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+                    f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+                    55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+                    be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+                    ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+                    22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+                    57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+                    68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+                    64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+                    39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+                    a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+                    87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+                    4b:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+            X509v3 Subject Key Identifier: 
+                B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+    Signature Algorithm: sha1WithRSAEncryption
+         76:56:58:36:0d:19:98:b4:d9:a5:cb:30:75:ae:b1:d6:80:97:
+         cc:ee:38:72:68:39:b0:02:3e:46:b6:c4:f2:ac:d1:d2:e1:66:
+         16:e6:85:a4:55:77:cb:2e:1c:59:dd:a5:4b:df:2f:33:bb:ce:
+         60:57:27:3a:a1:4d:49:6f:55:76:6d:d5:d7:c2:a0:5b:2a:9b:
+         f9:4b:f7:7f:21:dd:ee:5c:57:0d:00:35:3a:f1:8c:46:cb:04:
+         f6:46:8f:ce:05:6a:d5:c4:6c:fe:6e:98:bf:a4:9c:bd:8e:89:
+         2c:be:71:01:43:cc:36:2a:64:06:56:97:93:a5:47:bd:4a:3f:
+         8c:1b:75:c8:9e:b0:f0:25:98:77:21:c0:76:a7:51:7a:24:25:
+         7d:18:35:06:fe:c1:09:c5:0e:3b:99:a8:cd:9d:29:b0:3a:89:
+         f5:ea:e7:2a:e5:e2:24:4e:68:a9:1d:a7:dd:d2:08:4b:a1:d1:
+         6f:0c:bd:2c:e0:bb:7c:fa:a1:3c:65:cf:3a:52:4b:d3:20:7a:
+         0a:10:55:f8:ad:43:16:54:27:4e:53:73:c8:a3:96:89:d0:e1:
+         79:c6:09:78:d5:f5:bd:b1:b3:c5:7f:a6:4b:af:49:11:c8:97:
+         9c:4f:7c:70:69:16:5c:2d:b8:d0:df:1c:32:52:b9:de:f3:c3:
+         06:e8:83:22
+-----BEGIN CERTIFICATE-----
+MIIERjCCAy6gAwIBAgIEByd1ijANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEyMDcyNTE3NTgyOFoX
+DTE5MDcyNTE3NTc0NFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
+IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNl
+cnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9p
+pkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8Ok
+hPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ck
+RZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhak
+aHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDir
+CmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaOCAQAwgf0wEgYDVR0T
+AQH/BAgwBgEB/wIBATBTBgNVHSAETDBKMEgGCSsGAQQBsT4BADA7MDkGCCsGAQUF
+BwIBFi1odHRwOi8vY3liZXJ0cnVzdC5vbW5pcm9vdC5jb20vcmVwb3NpdG9yeS5j
+Zm0wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMEIGA1UdHwQ7MDkwN6A1oDOGMWh0dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5j
+b20vQ1JML09tbmlyb290MjAyNS5jcmwwHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoI
+Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQB2Vlg2DRmYtNmlyzB1rrHWgJfM7jhy
+aDmwAj5GtsTyrNHS4WYW5oWkVXfLLhxZ3aVL3y8zu85gVyc6oU1Jb1V2bdXXwqBb
+Kpv5S/d/Id3uXFcNADU68YxGywT2Ro/OBWrVxGz+bpi/pJy9joksvnEBQ8w2KmQG
+VpeTpUe9Sj+MG3XInrDwJZh3IcB2p1F6JCV9GDUG/sEJxQ47majNnSmwOon16ucq
+5eIkTmipHafd0ghLodFvDL0s4Lt8+qE8Zc86UkvTIHoKEFX4rUMWVCdOU3PIo5aJ
+0OF5xgl41fW9sbPFf6ZLr0kRyJecT3xwaRZcLbjQ3xwyUrne88MG6IMi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert26[] = {
+  0x30, 0x82, 0x04, 0x46, 0x30, 0x82, 0x03, 0x2e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x75, 0x8a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+  0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+  0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32,
+  0x30, 0x37, 0x32, 0x35, 0x31, 0x37, 0x35, 0x38, 0x32, 0x38, 0x5a, 0x17,
+  0x0d, 0x31, 0x39, 0x30, 0x37, 0x32, 0x35, 0x31, 0x37, 0x35, 0x37, 0x34,
+  0x34, 0x5a, 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+  0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+  0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63,
+  0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65,
+  0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75,
+  0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f,
+  0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xc6, 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d,
+  0x32, 0xa6, 0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71,
+  0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69,
+  0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41,
+  0x59, 0x3a, 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a,
+  0xcc, 0x4d, 0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3,
+  0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4,
+  0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92,
+  0xe3, 0x06, 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb,
+  0x5a, 0x89, 0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d,
+  0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24,
+  0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13,
+  0xeb, 0x51, 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81,
+  0x41, 0x50, 0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e,
+  0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4,
+  0x68, 0x73, 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c,
+  0x55, 0xdb, 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11,
+  0xb4, 0x29, 0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c,
+  0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab,
+  0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e,
+  0x87, 0x5d, 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b,
+  0x51, 0x38, 0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0x00, 0x30, 0x81, 0xfd, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x01, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a,
+  0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01,
+  0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f,
+  0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63,
+  0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82,
+  0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5,
+  0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b,
+  0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75,
+  0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72,
+  0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, 0x3e,
+  0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08,
+  0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x76, 0x56, 0x58, 0x36, 0x0d, 0x19, 0x98, 0xb4, 0xd9, 0xa5,
+  0xcb, 0x30, 0x75, 0xae, 0xb1, 0xd6, 0x80, 0x97, 0xcc, 0xee, 0x38, 0x72,
+  0x68, 0x39, 0xb0, 0x02, 0x3e, 0x46, 0xb6, 0xc4, 0xf2, 0xac, 0xd1, 0xd2,
+  0xe1, 0x66, 0x16, 0xe6, 0x85, 0xa4, 0x55, 0x77, 0xcb, 0x2e, 0x1c, 0x59,
+  0xdd, 0xa5, 0x4b, 0xdf, 0x2f, 0x33, 0xbb, 0xce, 0x60, 0x57, 0x27, 0x3a,
+  0xa1, 0x4d, 0x49, 0x6f, 0x55, 0x76, 0x6d, 0xd5, 0xd7, 0xc2, 0xa0, 0x5b,
+  0x2a, 0x9b, 0xf9, 0x4b, 0xf7, 0x7f, 0x21, 0xdd, 0xee, 0x5c, 0x57, 0x0d,
+  0x00, 0x35, 0x3a, 0xf1, 0x8c, 0x46, 0xcb, 0x04, 0xf6, 0x46, 0x8f, 0xce,
+  0x05, 0x6a, 0xd5, 0xc4, 0x6c, 0xfe, 0x6e, 0x98, 0xbf, 0xa4, 0x9c, 0xbd,
+  0x8e, 0x89, 0x2c, 0xbe, 0x71, 0x01, 0x43, 0xcc, 0x36, 0x2a, 0x64, 0x06,
+  0x56, 0x97, 0x93, 0xa5, 0x47, 0xbd, 0x4a, 0x3f, 0x8c, 0x1b, 0x75, 0xc8,
+  0x9e, 0xb0, 0xf0, 0x25, 0x98, 0x77, 0x21, 0xc0, 0x76, 0xa7, 0x51, 0x7a,
+  0x24, 0x25, 0x7d, 0x18, 0x35, 0x06, 0xfe, 0xc1, 0x09, 0xc5, 0x0e, 0x3b,
+  0x99, 0xa8, 0xcd, 0x9d, 0x29, 0xb0, 0x3a, 0x89, 0xf5, 0xea, 0xe7, 0x2a,
+  0xe5, 0xe2, 0x24, 0x4e, 0x68, 0xa9, 0x1d, 0xa7, 0xdd, 0xd2, 0x08, 0x4b,
+  0xa1, 0xd1, 0x6f, 0x0c, 0xbd, 0x2c, 0xe0, 0xbb, 0x7c, 0xfa, 0xa1, 0x3c,
+  0x65, 0xcf, 0x3a, 0x52, 0x4b, 0xd3, 0x20, 0x7a, 0x0a, 0x10, 0x55, 0xf8,
+  0xad, 0x43, 0x16, 0x54, 0x27, 0x4e, 0x53, 0x73, 0xc8, 0xa3, 0x96, 0x89,
+  0xd0, 0xe1, 0x79, 0xc6, 0x09, 0x78, 0xd5, 0xf5, 0xbd, 0xb1, 0xb3, 0xc5,
+  0x7f, 0xa6, 0x4b, 0xaf, 0x49, 0x11, 0xc8, 0x97, 0x9c, 0x4f, 0x7c, 0x70,
+  0x69, 0x16, 0x5c, 0x2d, 0xb8, 0xd0, 0xdf, 0x1c, 0x32, 0x52, 0xb9, 0xde,
+  0xf3, 0xc3, 0x06, 0xe8, 0x83, 0x22,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120019005 (0x727583d)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Jan 13 19:20:32 2010 GMT
+            Not After : Sep 30 18:19:47 2015 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+                    e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+                    a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+                    a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+                    cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+                    91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+                    f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+                    55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+                    be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+                    ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+                    22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+                    57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+                    68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+                    64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+                    39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+                    a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+                    87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+                    4b:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+    Signature Algorithm: sha1WithRSAEncryption
+         2e:76:85:d9:37:96:6d:af:89:f3:06:78:82:31:c4:46:07:1f:
+         65:c9:8e:b3:c9:54:78:e6:d1:42:df:75:2e:1e:55:ea:f7:fa:
+         9b:04:c0:75:7b:d1:79:3c:05:ec:79:c4:52:dd:a6:03:d7:a7:
+         50:99:3f:05:59:da:c6:55:f4:86:9c:0d:67:a3:49:04:95:32:
+         1d:c7:87:ec:85:af:64:6e:d5:c5:5f:09:a7:40:7d:16:ba:49:
+         0d:a2:fd:f6:df:55:30:6c:d7:78:c6:b9:cf:58:29:64:16:4c:
+         a3:20:81:47:b1:44:92:84:16:1b:6f:4a:bc:21:c6:0a:3d:ed:
+         33:ca
+-----BEGIN CERTIFICATE-----
+MIIETzCCA7igAwIBAgIEBydYPTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDExMzE5MjAzMloXDTE1MDkzMDE4MTk0N1owbDELMAkG
+A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
+Z2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYg
+Um9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S7
+5S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0
+OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChB
+VfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5l
+YYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBW
+BOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxB
+u24LUTi4S8sCAwEAAaOCAW8wggFrMBIGA1UdEwEB/wQIMAYBAf8CAQEwUwYDVR0g
+BEwwSjBIBgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYtaHR0cDovL2N5YmVydHJ1
+c3Qub21uaXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4GA1UdDwEB/wQEAwIBBjCB
+iQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29y
+cG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5j
+LjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1Ud
+HwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmlu
+L0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvD
+MA0GCSqGSIb3DQEBBQUAA4GBAC52hdk3lm2vifMGeIIxxEYHH2XJjrPJVHjm0ULf
+dS4eVer3+psEwHV70Xk8Bex5xFLdpgPXp1CZPwVZ2sZV9IacDWejSQSVMh3Hh+yF
+r2Ru1cVfCadAfRa6SQ2i/fbfVTBs13jGuc9YKWQWTKMggUexRJKEFhtvSrwhxgo9
+7TPK
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert27[] = {
+  0x30, 0x82, 0x04, 0x4f, 0x30, 0x82, 0x03, 0xb8, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x58, 0x3d, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x30, 0x30, 0x31, 0x31, 0x33, 0x31, 0x39, 0x32, 0x30, 0x33,
+  0x32, 0x5a, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x39, 0x33, 0x30, 0x31, 0x38,
+  0x31, 0x39, 0x34, 0x37, 0x5a, 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43,
+  0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69,
+  0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b,
+  0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67,
+  0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+  0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20,
+  0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xc6, 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb,
+  0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25,
+  0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c,
+  0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98,
+  0xa4, 0xa9, 0x41, 0x59, 0x3a, 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74,
+  0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12,
+  0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6,
+  0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec,
+  0xdb, 0x38, 0x92, 0xe3, 0x06, 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41,
+  0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d,
+  0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d,
+  0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04,
+  0xb7, 0xba, 0x13, 0xeb, 0x51, 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65,
+  0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a,
+  0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a,
+  0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71,
+  0xd3, 0x5a, 0x7c, 0x55, 0xdb, 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56,
+  0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2,
+  0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a,
+  0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1,
+  0xa1, 0x25, 0x1e, 0x87, 0x5d, 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41,
+  0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x01, 0x6f, 0x30, 0x82, 0x01, 0x6b, 0x30, 0x12, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+  0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04,
+  0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+  0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81,
+  0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1,
+  0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+  0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03,
+  0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72,
+  0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f,
+  0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f,
+  0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+  0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+  0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+  0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+  0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47,
+  0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x2e, 0x76, 0x85, 0xd9, 0x37,
+  0x96, 0x6d, 0xaf, 0x89, 0xf3, 0x06, 0x78, 0x82, 0x31, 0xc4, 0x46, 0x07,
+  0x1f, 0x65, 0xc9, 0x8e, 0xb3, 0xc9, 0x54, 0x78, 0xe6, 0xd1, 0x42, 0xdf,
+  0x75, 0x2e, 0x1e, 0x55, 0xea, 0xf7, 0xfa, 0x9b, 0x04, 0xc0, 0x75, 0x7b,
+  0xd1, 0x79, 0x3c, 0x05, 0xec, 0x79, 0xc4, 0x52, 0xdd, 0xa6, 0x03, 0xd7,
+  0xa7, 0x50, 0x99, 0x3f, 0x05, 0x59, 0xda, 0xc6, 0x55, 0xf4, 0x86, 0x9c,
+  0x0d, 0x67, 0xa3, 0x49, 0x04, 0x95, 0x32, 0x1d, 0xc7, 0x87, 0xec, 0x85,
+  0xaf, 0x64, 0x6e, 0xd5, 0xc5, 0x5f, 0x09, 0xa7, 0x40, 0x7d, 0x16, 0xba,
+  0x49, 0x0d, 0xa2, 0xfd, 0xf6, 0xdf, 0x55, 0x30, 0x6c, 0xd7, 0x78, 0xc6,
+  0xb9, 0xcf, 0x58, 0x29, 0x64, 0x16, 0x4c, 0xa3, 0x20, 0x81, 0x47, 0xb1,
+  0x44, 0x92, 0x84, 0x16, 0x1b, 0x6f, 0x4a, 0xbc, 0x21, 0xc6, 0x0a, 0x3d,
+  0xed, 0x33, 0xca,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:41:43
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Domain Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:a3:cd:c0:df:33:40:26:eb:de:5a:d7:94:66:
+                    d4:01:63:cc:33:44:89:e0:e2:b8:c2:47:0d:8f:ad:
+                    69:86:1c:a8:73:42:0b:f1:72:fb:2d:ac:b5:11:72:
+                    83:22:f6:56:e7:2e:c5:67:71:9d:00:1c:32:bc:e3:
+                    ed:2e:08:45:a9:e6:fa:dd:c8:8c:83:05:c1:6f:4b:
+                    d0:26:4a:0b:f6:1b:45:c0:4d:7e:93:bc:0d:27:84:
+                    ed:30:a3:e9:c6:26:26:dd:2d:1f:d8:8b:c3:ce:19:
+                    d0:5b:fc:08:9f:e4:d8:e2:35:e4:a0:68:a6:f6:0d:
+                    a3:74:60:42:b2:97:82:24:8e:41:a4:f2:2e:5e:b6:
+                    8e:a7:6e:d9:6c:7f:0d:3b:24:35:6a:d0:ab:5b:6a:
+                    f7:97:02:00:3f:51:a6:a7:6e:73:ca:77:0d:76:7c:
+                    9b:b6:30:1a:1a:9c:f7:1f:28:7b:0e:8b:47:1f:e7:
+                    7f:05:8c:c6:c9:c8:bb:cf:e9:dc:7a:41:2e:a1:86:
+                    da:d4:39:b2:e2:13:40:a6:a8:3a:fa:0f:53:1e:4f:
+                    ec:6e:98:09:1b:ca:9a:77:b3:55:85:85:e9:2e:16:
+                    b5:9d:5e:54:f1:4a:7a:6c:39:ba:6e:17:06:34:b3:
+                    b2:42:e1:f7:f3:9c:9a:0b:11:44:de:6a:78:8e:b1:
+                    13:4f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                96:AD:FA:B0:5B:B9:83:64:2A:76:C2:1C:8A:69:DA:42:DC:FE:FD:28
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3a:e7:fc:ae:af:05:43:80:27:75:41:5f:a8:f0:28:8f:1f:8f:
+         83:7e:b2:b8:ba:ae:75:31:27:88:a5:e5:b9:4e:04:43:d2:ad:
+         e8:13:00:a3:db:19:01:30:9e:6c:3c:52:7f:5c:de:ab:67:c3:
+         84:04:54:51:99:9e:63:2f:bd:d5:b7:c0:d5:da:03:0e:49:d3:
+         e1:b3:92:4f:df:92:4e:7d:ae:22:6a:ce:d8:bc:fc:7c:ae:6b:
+         b6:8a:ea:45:62:90:11:d3:0b:71:a7:5e:06:22:ff:4d:38:ea:
+         b9:3a:6e:cd:67:1a:02:7f:4b:f3:bf:0e:79:6f:be:d5:29:32:
+         59:59:1d:96:08:9b:70:8f:f7:1e:5c:46:7b:4e:d0:9d:b4:53:
+         c8:12:02:1b:0d:bb:32:eb:59:53:b9:3e:1b:56:8d:15:c8:f1:
+         42:3f:77:fe:1f:e5:6d:9e:66:1f:ab:da:b2:83:57:b4:0c:22:
+         d2:86:bc:da:32:d7:c0:ed:70:85:7c:93:aa:f0:97:dc:39:11:
+         d2:d8:89:eb:8d:90:a3:b6:50:25:cb:6c:d9:a6:c3:6f:fb:88:
+         54:b8:e4:92:70:87:ce:79:3b:f0:de:36:bf:03:04:00:3d:f9:
+         ef:9e:a9:67:a4:f4:86:3e:23:97:b8:2a:71:e2:ed:fe:69:88:
+         67:bf:26:5c
+-----BEGIN CERTIFICATE-----
+MIIEWjCCA0KgAwIBAgILBAAAAAABL07hQUMwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0
+aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxo83A
+3zNAJuveWteUZtQBY8wzRIng4rjCRw2PrWmGHKhzQgvxcvstrLURcoMi9lbnLsVn
+cZ0AHDK84+0uCEWp5vrdyIyDBcFvS9AmSgv2G0XATX6TvA0nhO0wo+nGJibdLR/Y
+i8POGdBb/Aif5NjiNeSgaKb2DaN0YEKyl4IkjkGk8i5eto6nbtlsfw07JDVq0Ktb
+aveXAgA/UaanbnPKdw12fJu2MBoanPcfKHsOi0cf538FjMbJyLvP6dx6QS6hhtrU
+ObLiE0CmqDr6D1MeT+xumAkbypp3s1WFhekuFrWdXlTxSnpsObpuFwY0s7JC4ffz
+nJoLEUTeaniOsRNPAgMBAAGjggElMIIBITAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUlq36sFu5g2QqdsIcimnaQtz+/SgwRwYD
+VR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jh
+bHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9j
+cmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEwHwYDVR0j
+BBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQADggEBADrn
+/K6vBUOAJ3VBX6jwKI8fj4N+sri6rnUxJ4il5blOBEPSregTAKPbGQEwnmw8Un9c
+3qtnw4QEVFGZnmMvvdW3wNXaAw5J0+Gzkk/fkk59riJqzti8/Hyua7aK6kVikBHT
+C3GnXgYi/0046rk6bs1nGgJ/S/O/DnlvvtUpMllZHZYIm3CP9x5cRntO0J20U8gS
+AhsNuzLrWVO5PhtWjRXI8UI/d/4f5W2eZh+r2rKDV7QMItKGvNoy18DtcIV8k6rw
+l9w5EdLYieuNkKO2UCXLbNmmw2/7iFS45JJwh855O/DeNr8DBAA9+e+eqWek9IY+
+I5e4KnHi7f5piGe/Jlw=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert28[] = {
+  0x30, 0x82, 0x04, 0x5a, 0x30, 0x82, 0x03, 0x42, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x41, 0x43, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+  0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+  0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x47,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f,
+  0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xa3, 0xcd, 0xc0,
+  0xdf, 0x33, 0x40, 0x26, 0xeb, 0xde, 0x5a, 0xd7, 0x94, 0x66, 0xd4, 0x01,
+  0x63, 0xcc, 0x33, 0x44, 0x89, 0xe0, 0xe2, 0xb8, 0xc2, 0x47, 0x0d, 0x8f,
+  0xad, 0x69, 0x86, 0x1c, 0xa8, 0x73, 0x42, 0x0b, 0xf1, 0x72, 0xfb, 0x2d,
+  0xac, 0xb5, 0x11, 0x72, 0x83, 0x22, 0xf6, 0x56, 0xe7, 0x2e, 0xc5, 0x67,
+  0x71, 0x9d, 0x00, 0x1c, 0x32, 0xbc, 0xe3, 0xed, 0x2e, 0x08, 0x45, 0xa9,
+  0xe6, 0xfa, 0xdd, 0xc8, 0x8c, 0x83, 0x05, 0xc1, 0x6f, 0x4b, 0xd0, 0x26,
+  0x4a, 0x0b, 0xf6, 0x1b, 0x45, 0xc0, 0x4d, 0x7e, 0x93, 0xbc, 0x0d, 0x27,
+  0x84, 0xed, 0x30, 0xa3, 0xe9, 0xc6, 0x26, 0x26, 0xdd, 0x2d, 0x1f, 0xd8,
+  0x8b, 0xc3, 0xce, 0x19, 0xd0, 0x5b, 0xfc, 0x08, 0x9f, 0xe4, 0xd8, 0xe2,
+  0x35, 0xe4, 0xa0, 0x68, 0xa6, 0xf6, 0x0d, 0xa3, 0x74, 0x60, 0x42, 0xb2,
+  0x97, 0x82, 0x24, 0x8e, 0x41, 0xa4, 0xf2, 0x2e, 0x5e, 0xb6, 0x8e, 0xa7,
+  0x6e, 0xd9, 0x6c, 0x7f, 0x0d, 0x3b, 0x24, 0x35, 0x6a, 0xd0, 0xab, 0x5b,
+  0x6a, 0xf7, 0x97, 0x02, 0x00, 0x3f, 0x51, 0xa6, 0xa7, 0x6e, 0x73, 0xca,
+  0x77, 0x0d, 0x76, 0x7c, 0x9b, 0xb6, 0x30, 0x1a, 0x1a, 0x9c, 0xf7, 0x1f,
+  0x28, 0x7b, 0x0e, 0x8b, 0x47, 0x1f, 0xe7, 0x7f, 0x05, 0x8c, 0xc6, 0xc9,
+  0xc8, 0xbb, 0xcf, 0xe9, 0xdc, 0x7a, 0x41, 0x2e, 0xa1, 0x86, 0xda, 0xd4,
+  0x39, 0xb2, 0xe2, 0x13, 0x40, 0xa6, 0xa8, 0x3a, 0xfa, 0x0f, 0x53, 0x1e,
+  0x4f, 0xec, 0x6e, 0x98, 0x09, 0x1b, 0xca, 0x9a, 0x77, 0xb3, 0x55, 0x85,
+  0x85, 0xe9, 0x2e, 0x16, 0xb5, 0x9d, 0x5e, 0x54, 0xf1, 0x4a, 0x7a, 0x6c,
+  0x39, 0xba, 0x6e, 0x17, 0x06, 0x34, 0xb3, 0xb2, 0x42, 0xe1, 0xf7, 0xf3,
+  0x9c, 0x9a, 0x0b, 0x11, 0x44, 0xde, 0x6a, 0x78, 0x8e, 0xb1, 0x13, 0x4f,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, 0x30, 0x82, 0x01,
+  0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x96, 0xad, 0xfa, 0xb0, 0x5b, 0xb9, 0x83, 0x64, 0x2a, 0x76, 0xc2, 0x1c,
+  0x8a, 0x69, 0xda, 0x42, 0xdc, 0xfe, 0xfd, 0x28, 0x30, 0x47, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+  0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d,
+  0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc,
+  0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3a, 0xe7,
+  0xfc, 0xae, 0xaf, 0x05, 0x43, 0x80, 0x27, 0x75, 0x41, 0x5f, 0xa8, 0xf0,
+  0x28, 0x8f, 0x1f, 0x8f, 0x83, 0x7e, 0xb2, 0xb8, 0xba, 0xae, 0x75, 0x31,
+  0x27, 0x88, 0xa5, 0xe5, 0xb9, 0x4e, 0x04, 0x43, 0xd2, 0xad, 0xe8, 0x13,
+  0x00, 0xa3, 0xdb, 0x19, 0x01, 0x30, 0x9e, 0x6c, 0x3c, 0x52, 0x7f, 0x5c,
+  0xde, 0xab, 0x67, 0xc3, 0x84, 0x04, 0x54, 0x51, 0x99, 0x9e, 0x63, 0x2f,
+  0xbd, 0xd5, 0xb7, 0xc0, 0xd5, 0xda, 0x03, 0x0e, 0x49, 0xd3, 0xe1, 0xb3,
+  0x92, 0x4f, 0xdf, 0x92, 0x4e, 0x7d, 0xae, 0x22, 0x6a, 0xce, 0xd8, 0xbc,
+  0xfc, 0x7c, 0xae, 0x6b, 0xb6, 0x8a, 0xea, 0x45, 0x62, 0x90, 0x11, 0xd3,
+  0x0b, 0x71, 0xa7, 0x5e, 0x06, 0x22, 0xff, 0x4d, 0x38, 0xea, 0xb9, 0x3a,
+  0x6e, 0xcd, 0x67, 0x1a, 0x02, 0x7f, 0x4b, 0xf3, 0xbf, 0x0e, 0x79, 0x6f,
+  0xbe, 0xd5, 0x29, 0x32, 0x59, 0x59, 0x1d, 0x96, 0x08, 0x9b, 0x70, 0x8f,
+  0xf7, 0x1e, 0x5c, 0x46, 0x7b, 0x4e, 0xd0, 0x9d, 0xb4, 0x53, 0xc8, 0x12,
+  0x02, 0x1b, 0x0d, 0xbb, 0x32, 0xeb, 0x59, 0x53, 0xb9, 0x3e, 0x1b, 0x56,
+  0x8d, 0x15, 0xc8, 0xf1, 0x42, 0x3f, 0x77, 0xfe, 0x1f, 0xe5, 0x6d, 0x9e,
+  0x66, 0x1f, 0xab, 0xda, 0xb2, 0x83, 0x57, 0xb4, 0x0c, 0x22, 0xd2, 0x86,
+  0xbc, 0xda, 0x32, 0xd7, 0xc0, 0xed, 0x70, 0x85, 0x7c, 0x93, 0xaa, 0xf0,
+  0x97, 0xdc, 0x39, 0x11, 0xd2, 0xd8, 0x89, 0xeb, 0x8d, 0x90, 0xa3, 0xb6,
+  0x50, 0x25, 0xcb, 0x6c, 0xd9, 0xa6, 0xc3, 0x6f, 0xfb, 0x88, 0x54, 0xb8,
+  0xe4, 0x92, 0x70, 0x87, 0xce, 0x79, 0x3b, 0xf0, 0xde, 0x36, 0xbf, 0x03,
+  0x04, 0x00, 0x3d, 0xf9, 0xef, 0x9e, 0xa9, 0x67, 0xa4, 0xf4, 0x86, 0x3e,
+  0x23, 0x97, 0xb8, 0x2a, 0x71, 0xe2, 0xed, 0xfe, 0x69, 0x88, 0x67, 0xbf,
+  0x26, 0x5c,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:5b:63
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:a1:46:cc:52:9a:b8:a5:b4:7f:58:d9:cd:d8:
+                    4b:a0:72:0c:97:5b:a6:88:20:c3:b9:3d:46:dc:d0:
+                    5c:52:30:f6:fa:59:4f:85:5f:b0:db:88:b4:a9:5f:
+                    2b:23:48:ac:ab:f5:92:78:14:b6:32:0f:fb:5c:6a:
+                    85:5b:00:90:e0:bb:65:f5:5a:f9:4f:67:7e:c7:6c:
+                    29:ec:93:c0:2b:ca:c4:5e:d8:b0:db:d6:be:3f:9b:
+                    0b:c0:8f:a9:5d:ae:f7:00:02:a4:fc:ba:66:11:38:
+                    77:fe:23:20:25:55:10:c5:bd:82:b9:4c:b1:68:c6:
+                    e2:70:7b:83:5c:13:67:c1:a1:f3:7c:0b:a8:99:9a:
+                    d0:e2:9b:25:31:c8:2b:8d:40:f6:52:63:b1:a0:ad:
+                    5a:2e:f5:79:36:6d:35:2c:0e:dd:05:e4:d0:e2:07:
+                    48:b7:28:5e:2b:d5:58:d5:6c:d0:0c:a1:01:46:01:
+                    5a:8f:c6:af:64:c7:55:01:5d:e1:d1:c6:6c:50:25:
+                    a0:05:ad:00:ab:0c:8d:65:6b:dd:eb:c2:72:54:c9:
+                    0f:3c:00:17:87:22:ef:db:b9:86:78:16:51:ae:77:
+                    d9:a6:28:4d:f3:58:8d:83:67:b9:34:25:9b:1c:51:
+                    80:51:f3:83:92:6a:a3:ae:47:9a:d6:e4:8b:1b:c0:
+                    ed:b1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                B0:B0:4A:FD:1C:75:28:F8:1C:61:AA:13:F6:FA:C1:90:3D:6B:16:A3
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root-r2.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/ExtendedSSLCA
+
+            X509v3 Authority Key Identifier: 
+                keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+
+    Signature Algorithm: sha1WithRSAEncryption
+         5f:28:90:0c:2d:e9:20:b2:30:7c:88:ab:40:05:fa:b1:9d:5c:
+         22:93:d5:9d:ca:35:31:fa:2c:ea:1d:93:59:19:c4:a0:0d:fb:
+         09:40:31:da:64:56:cd:52:be:e7:18:66:e8:6d:09:9b:b2:db:
+         94:3e:ee:36:45:1e:24:54:b6:20:05:93:b5:31:1a:b8:64:57:
+         e6:d3:2c:01:4c:39:96:79:fe:b7:04:98:12:ef:b7:2e:5a:77:
+         fe:47:f3:79:98:42:dd:16:be:5b:69:2b:c9:26:c8:29:68:77:
+         e6:ac:f6:4e:90:13:28:67:04:ec:72:25:1f:d7:a7:0a:50:7f:
+         38:0e:72:18:b1:29:b8:ff:ae:a1:d4:54:b8:66:4d:a0:d5:cf:
+         d3:ef:a9:32:2a:c5:97:62:d2:84:cc:b0:a0:d8:98:a9:ca:38:
+         e4:cc:44:35:6f:61:26:b0:2e:98:72:f9:38:32:0d:b4:a1:62:
+         0a:21:62:15:de:bb:6d:93:10:36:53:3b:4a:21:7b:c2:f5:be:
+         2e:f6:02:13:e9:ae:4c:70:e9:2a:f6:1f:c3:8b:e5:9f:e0:8d:
+         2a:28:e8:19:2c:b3:65:dd:f7:f1:6f:97:35:9e:db:92:35:63:
+         81:d7:27:e4:2b:62:aa:fa:62:a1:71:92:8c:0a:16:b7:3d:b5:
+         4a:65:5b:02
+-----BEGIN CERTIFICATE-----
+MIIEWzCCA0OgAwIBAgILBAAAAAABL07hW2MwDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTEwNDEzMTAwMDAwWhcNMjIwNDEz
+MTAwMDAwWjBZMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1z
+YTEvMC0GA1UEAxMmR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIC0g
+RzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNoUbMUpq4pbR/WNnN
+2EugcgyXW6aIIMO5PUbc0FxSMPb6WU+FX7DbiLSpXysjSKyr9ZJ4FLYyD/tcaoVb
+AJDgu2X1WvlPZ37HbCnsk8ArysRe2LDb1r4/mwvAj6ldrvcAAqT8umYROHf+IyAl
+VRDFvYK5TLFoxuJwe4NcE2fBofN8C6iZmtDimyUxyCuNQPZSY7GgrVou9Xk2bTUs
+Dt0F5NDiB0i3KF4r1VjVbNAMoQFGAVqPxq9kx1UBXeHRxmxQJaAFrQCrDI1la93r
+wnJUyQ88ABeHIu/buYZ4FlGud9mmKE3zWI2DZ7k0JZscUYBR84OSaqOuR5rW5Isb
+wO2xAgMBAAGjggEvMIIBKzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADAdBgNVHQ4EFgQUsLBK/Rx1KPgcYaoT9vrBkD1rFqMwRwYDVR0gBEAwPjA8
+BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t
+L3JlcG9zaXRvcnkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFs
+c2lnbi5uZXQvcm9vdC1yMi5jcmwwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzAB
+hihodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMB8GA1Ud
+IwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMA0GCSqGSIb3DQEBBQUAA4IBAQBf
+KJAMLekgsjB8iKtABfqxnVwik9WdyjUx+izqHZNZGcSgDfsJQDHaZFbNUr7nGGbo
+bQmbstuUPu42RR4kVLYgBZO1MRq4ZFfm0ywBTDmWef63BJgS77cuWnf+R/N5mELd
+Fr5baSvJJsgpaHfmrPZOkBMoZwTsciUf16cKUH84DnIYsSm4/66h1FS4Zk2g1c/T
+76kyKsWXYtKEzLCg2JipyjjkzEQ1b2EmsC6Ycvk4Mg20oWIKIWIV3rttkxA2UztK
+IXvC9b4u9gIT6a5McOkq9h/Di+Wf4I0qKOgZLLNl3ffxb5c1ntuSNWOB1yfkK2Kq
++mKhcZKMCha3PbVKZVsC
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert29[] = {
+  0x30, 0x82, 0x04, 0x5b, 0x30, 0x82, 0x03, 0x43, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x5b, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33,
+  0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x59, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73,
+  0x61, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+  0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69,
+  0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+  0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+  0xa1, 0x46, 0xcc, 0x52, 0x9a, 0xb8, 0xa5, 0xb4, 0x7f, 0x58, 0xd9, 0xcd,
+  0xd8, 0x4b, 0xa0, 0x72, 0x0c, 0x97, 0x5b, 0xa6, 0x88, 0x20, 0xc3, 0xb9,
+  0x3d, 0x46, 0xdc, 0xd0, 0x5c, 0x52, 0x30, 0xf6, 0xfa, 0x59, 0x4f, 0x85,
+  0x5f, 0xb0, 0xdb, 0x88, 0xb4, 0xa9, 0x5f, 0x2b, 0x23, 0x48, 0xac, 0xab,
+  0xf5, 0x92, 0x78, 0x14, 0xb6, 0x32, 0x0f, 0xfb, 0x5c, 0x6a, 0x85, 0x5b,
+  0x00, 0x90, 0xe0, 0xbb, 0x65, 0xf5, 0x5a, 0xf9, 0x4f, 0x67, 0x7e, 0xc7,
+  0x6c, 0x29, 0xec, 0x93, 0xc0, 0x2b, 0xca, 0xc4, 0x5e, 0xd8, 0xb0, 0xdb,
+  0xd6, 0xbe, 0x3f, 0x9b, 0x0b, 0xc0, 0x8f, 0xa9, 0x5d, 0xae, 0xf7, 0x00,
+  0x02, 0xa4, 0xfc, 0xba, 0x66, 0x11, 0x38, 0x77, 0xfe, 0x23, 0x20, 0x25,
+  0x55, 0x10, 0xc5, 0xbd, 0x82, 0xb9, 0x4c, 0xb1, 0x68, 0xc6, 0xe2, 0x70,
+  0x7b, 0x83, 0x5c, 0x13, 0x67, 0xc1, 0xa1, 0xf3, 0x7c, 0x0b, 0xa8, 0x99,
+  0x9a, 0xd0, 0xe2, 0x9b, 0x25, 0x31, 0xc8, 0x2b, 0x8d, 0x40, 0xf6, 0x52,
+  0x63, 0xb1, 0xa0, 0xad, 0x5a, 0x2e, 0xf5, 0x79, 0x36, 0x6d, 0x35, 0x2c,
+  0x0e, 0xdd, 0x05, 0xe4, 0xd0, 0xe2, 0x07, 0x48, 0xb7, 0x28, 0x5e, 0x2b,
+  0xd5, 0x58, 0xd5, 0x6c, 0xd0, 0x0c, 0xa1, 0x01, 0x46, 0x01, 0x5a, 0x8f,
+  0xc6, 0xaf, 0x64, 0xc7, 0x55, 0x01, 0x5d, 0xe1, 0xd1, 0xc6, 0x6c, 0x50,
+  0x25, 0xa0, 0x05, 0xad, 0x00, 0xab, 0x0c, 0x8d, 0x65, 0x6b, 0xdd, 0xeb,
+  0xc2, 0x72, 0x54, 0xc9, 0x0f, 0x3c, 0x00, 0x17, 0x87, 0x22, 0xef, 0xdb,
+  0xb9, 0x86, 0x78, 0x16, 0x51, 0xae, 0x77, 0xd9, 0xa6, 0x28, 0x4d, 0xf3,
+  0x58, 0x8d, 0x83, 0x67, 0xb9, 0x34, 0x25, 0x9b, 0x1c, 0x51, 0x80, 0x51,
+  0xf3, 0x83, 0x92, 0x6a, 0xa3, 0xae, 0x47, 0x9a, 0xd6, 0xe4, 0x8b, 0x1b,
+  0xc0, 0xed, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x2f,
+  0x30, 0x82, 0x01, 0x2b, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0xb0, 0xb0, 0x4a, 0xfd, 0x1c, 0x75, 0x28, 0xf8, 0x1c,
+  0x61, 0xaa, 0x13, 0xf6, 0xfa, 0xc1, 0x90, 0x3d, 0x6b, 0x16, 0xa3, 0x30,
+  0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74,
+  0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f,
+  0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30,
+  0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f,
+  0x74, 0x2d, 0x72, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x44, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x38, 0x30, 0x36,
+  0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+  0x64, 0x53, 0x53, 0x4c, 0x43, 0x41, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x9b, 0xe2, 0x07, 0x57, 0x67,
+  0x1c, 0x1e, 0xc0, 0x6a, 0x06, 0xde, 0x59, 0xb4, 0x9a, 0x2d, 0xdf, 0xdc,
+  0x19, 0x86, 0x2e, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5f,
+  0x28, 0x90, 0x0c, 0x2d, 0xe9, 0x20, 0xb2, 0x30, 0x7c, 0x88, 0xab, 0x40,
+  0x05, 0xfa, 0xb1, 0x9d, 0x5c, 0x22, 0x93, 0xd5, 0x9d, 0xca, 0x35, 0x31,
+  0xfa, 0x2c, 0xea, 0x1d, 0x93, 0x59, 0x19, 0xc4, 0xa0, 0x0d, 0xfb, 0x09,
+  0x40, 0x31, 0xda, 0x64, 0x56, 0xcd, 0x52, 0xbe, 0xe7, 0x18, 0x66, 0xe8,
+  0x6d, 0x09, 0x9b, 0xb2, 0xdb, 0x94, 0x3e, 0xee, 0x36, 0x45, 0x1e, 0x24,
+  0x54, 0xb6, 0x20, 0x05, 0x93, 0xb5, 0x31, 0x1a, 0xb8, 0x64, 0x57, 0xe6,
+  0xd3, 0x2c, 0x01, 0x4c, 0x39, 0x96, 0x79, 0xfe, 0xb7, 0x04, 0x98, 0x12,
+  0xef, 0xb7, 0x2e, 0x5a, 0x77, 0xfe, 0x47, 0xf3, 0x79, 0x98, 0x42, 0xdd,
+  0x16, 0xbe, 0x5b, 0x69, 0x2b, 0xc9, 0x26, 0xc8, 0x29, 0x68, 0x77, 0xe6,
+  0xac, 0xf6, 0x4e, 0x90, 0x13, 0x28, 0x67, 0x04, 0xec, 0x72, 0x25, 0x1f,
+  0xd7, 0xa7, 0x0a, 0x50, 0x7f, 0x38, 0x0e, 0x72, 0x18, 0xb1, 0x29, 0xb8,
+  0xff, 0xae, 0xa1, 0xd4, 0x54, 0xb8, 0x66, 0x4d, 0xa0, 0xd5, 0xcf, 0xd3,
+  0xef, 0xa9, 0x32, 0x2a, 0xc5, 0x97, 0x62, 0xd2, 0x84, 0xcc, 0xb0, 0xa0,
+  0xd8, 0x98, 0xa9, 0xca, 0x38, 0xe4, 0xcc, 0x44, 0x35, 0x6f, 0x61, 0x26,
+  0xb0, 0x2e, 0x98, 0x72, 0xf9, 0x38, 0x32, 0x0d, 0xb4, 0xa1, 0x62, 0x0a,
+  0x21, 0x62, 0x15, 0xde, 0xbb, 0x6d, 0x93, 0x10, 0x36, 0x53, 0x3b, 0x4a,
+  0x21, 0x7b, 0xc2, 0xf5, 0xbe, 0x2e, 0xf6, 0x02, 0x13, 0xe9, 0xae, 0x4c,
+  0x70, 0xe9, 0x2a, 0xf6, 0x1f, 0xc3, 0x8b, 0xe5, 0x9f, 0xe0, 0x8d, 0x2a,
+  0x28, 0xe8, 0x19, 0x2c, 0xb3, 0x65, 0xdd, 0xf7, 0xf1, 0x6f, 0x97, 0x35,
+  0x9e, 0xdb, 0x92, 0x35, 0x63, 0x81, 0xd7, 0x27, 0xe4, 0x2b, 0x62, 0xaa,
+  0xfa, 0x62, 0xa1, 0x71, 0x92, 0x8c, 0x0a, 0x16, 0xb7, 0x3d, 0xb5, 0x4a,
+  0x65, 0x5b, 0x02,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120013506 (0x72742c2)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Validity
+            Not Before: Nov 30 16:24:37 2010 GMT
+            Not After : Nov 30 16:23:46 2017 GMT
+        Subject: C=DE, O=T-Systems International GmbH, OU=Trust Center Services, CN=TeleSec ServerPass CA 1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e6:e8:cd:30:21:8d:b9:8f:ee:f7:54:57:ab:5c:
+                    da:9d:70:d0:20:7a:a6:89:f8:e6:21:7d:97:1b:69:
+                    00:83:1d:4f:ba:d2:98:6d:0b:c7:ed:8f:26:01:a6:
+                    66:1c:48:f5:d5:d3:db:0a:ad:bf:77:44:7e:06:33:
+                    90:f7:e7:28:f5:82:c3:f0:43:49:76:ed:73:77:5d:
+                    83:e9:6b:18:01:ea:ee:4f:1d:b2:49:e0:32:02:d1:
+                    25:36:dc:6f:28:64:bf:89:34:83:0f:ea:03:56:df:
+                    b6:57:93:bf:c6:56:f7:f0:dd:50:ac:b6:4f:4d:2a:
+                    53:11:f1:d3:24:2f:11:39:0b:05:57:1f:de:3d:71:
+                    1a:2f:b4:9c:f5:ab:e3:d2:d1:d1:29:c0:36:91:21:
+                    61:e7:c5:d0:f6:40:da:e9:f1:4c:96:6c:d7:c9:13:
+                    09:a5:22:5d:06:f7:0c:3d:65:a3:fc:52:b9:7e:72:
+                    bd:33:7b:dd:7c:ae:7a:2b:c1:4f:aa:fc:c8:f8:c5:
+                    9d:25:86:53:55:74:bc:1e:ca:42:4a:33:e2:12:2d:
+                    dc:d2:29:d9:7b:ad:3a:40:ec:01:d1:05:ec:8a:9c:
+                    23:ea:86:30:be:f3:e6:e9:08:cc:9e:b4:40:8c:af:
+                    02:e7:7e:3f:7e:e2:c1:08:02:d7:23:29:63:7b:eb:
+                    82:55
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+                Policy: 1.3.6.1.4.1.7879.13.2
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+            X509v3 Subject Key Identifier: 
+                33:DC:9E:96:EC:D8:E8:35:1F:6D:90:1B:0B:38:A4:AF:74:1B:C6:58
+    Signature Algorithm: sha1WithRSAEncryption
+         74:76:a8:19:77:97:5c:af:5b:8f:e5:e5:ec:dd:59:bb:90:e6:
+         52:4c:1e:c4:fe:da:34:e6:2a:fc:3c:dd:b0:7b:69:57:82:c5:
+         24:26:0c:74:1e:d9:a0:7d:7e:8b:fa:76:ab:a1:7d:58:ae:34:
+         12:fb:99:24:14:94:31:15:62:d7:d9:5c:79:33:1c:e2:1b:91:
+         13:fd:f8:cf:1c:ff:2c:cc:b5:ed:27:8e:b9:97:30:b1:de:e9:
+         e6:d2:10:e3:a6:d7:9a:f0:39:1a:4e:3b:ae:e2:0b:b2:ea:0d:
+         80:61:31:33:cc:73:f1:d3:0c:e9:31:26:9e:78:d9:08:67:93:
+         71:e8:b6:f1:a3:66:fd:00:46:0c:7f:da:c3:fc:63:d8:c5:3f:
+         c8:23:70:b9:a8:60:c2:5e:47:a8:8d:19:89:63:a7:a0:69:07:
+         9c:27:54:22:2c:4e:c7:3d:99:3c:b9:fc:93:80:65:bd:bb:d0:
+         f8:ea:8c:fb:71:e1:e8:a6:07:40:f1:d0:05:85:da:38:71:59:
+         f4:98:b7:64:d3:76:10:f5:b9:6a:d7:15:0e:58:fe:e6:6b:29:
+         4a:ee:75:88:9f:5f:66:80:07:6b:6b:55:68:bc:66:39:c5:9f:
+         57:fe:35:f5:89:0f:07:57:fa:d1:57:ef:4d:b2:d7:77:c8:bc:
+         f8:65:0f:9e
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIEBydCwjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEwMTEzMDE2MjQzN1oX
+DTE3MTEzMDE2MjM0NlowdjELMAkGA1UEBhMCREUxJTAjBgNVBAoTHFQtU3lzdGVt
+cyBJbnRlcm5hdGlvbmFsIEdtYkgxHjAcBgNVBAsTFVRydXN0IENlbnRlciBTZXJ2
+aWNlczEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIENBIDEwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm6M0wIY25j+73VFerXNqdcNAgeqaJ+OYh
+fZcbaQCDHU+60phtC8ftjyYBpmYcSPXV09sKrb93RH4GM5D35yj1gsPwQ0l27XN3
+XYPpaxgB6u5PHbJJ4DIC0SU23G8oZL+JNIMP6gNW37ZXk7/GVvfw3VCstk9NKlMR
+8dMkLxE5CwVXH949cRovtJz1q+PS0dEpwDaRIWHnxdD2QNrp8UyWbNfJEwmlIl0G
+9ww9ZaP8Url+cr0ze918rnorwU+q/Mj4xZ0lhlNVdLweykJKM+ISLdzSKdl7rTpA
+7AHRBeyKnCPqhjC+8+bpCMyetECMrwLnfj9+4sEIAtcjKWN764JVAgMBAAGjggEO
+MIIBCjASBgNVHRMBAf8ECDAGAQH/AgEAMGAGA1UdIARZMFcwSAYJKwYBBAGxPgEA
+MDswOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9y
+ZXBvc2l0b3J5LmNmbTALBgkrBgEEAb1HDQIwDgYDVR0PAQH/BAQDAgEGMB8GA1Ud
+IwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1BE3wMEIGA1UdHwQ7MDkwN6A1oDOGMWh0
+dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5jb20vQ1JML09tbmlyb290MjAyNS5jcmww
+HQYDVR0OBBYEFDPcnpbs2Og1H22QGws4pK90G8ZYMA0GCSqGSIb3DQEBBQUAA4IB
+AQB0dqgZd5dcr1uP5eXs3Vm7kOZSTB7E/to05ir8PN2we2lXgsUkJgx0HtmgfX6L
++naroX1YrjQS+5kkFJQxFWLX2Vx5MxziG5ET/fjPHP8szLXtJ465lzCx3unm0hDj
+ptea8DkaTjuu4guy6g2AYTEzzHPx0wzpMSaeeNkIZ5Nx6Lbxo2b9AEYMf9rD/GPY
+xT/II3C5qGDCXkeojRmJY6egaQecJ1QiLE7HPZk8ufyTgGW9u9D46oz7ceHopgdA
+8dAFhdo4cVn0mLdk03YQ9blq1xUOWP7maylK7nWIn19mgAdra1VovGY5xZ9X/jX1
+iQ8HV/rRV+9Nstd3yLz4ZQ+e
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert30[] = {
+  0x30, 0x82, 0x04, 0x5e, 0x30, 0x82, 0x03, 0x46, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x42, 0xc2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+  0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+  0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30,
+  0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x32, 0x34, 0x33, 0x37, 0x5a, 0x17,
+  0x0d, 0x31, 0x37, 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x32, 0x33, 0x34,
+  0x36, 0x5a, 0x30, 0x76, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+  0x06, 0x13, 0x02, 0x44, 0x45, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x1c, 0x54, 0x2d, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+  0x73, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x61, 0x6c, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x31, 0x1e, 0x30, 0x1c,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x15, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x20, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x72, 0x76,
+  0x69, 0x63, 0x65, 0x73, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x17, 0x54, 0x65, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x20, 0x53,
+  0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x20, 0x43, 0x41,
+  0x20, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe6,
+  0xe8, 0xcd, 0x30, 0x21, 0x8d, 0xb9, 0x8f, 0xee, 0xf7, 0x54, 0x57, 0xab,
+  0x5c, 0xda, 0x9d, 0x70, 0xd0, 0x20, 0x7a, 0xa6, 0x89, 0xf8, 0xe6, 0x21,
+  0x7d, 0x97, 0x1b, 0x69, 0x00, 0x83, 0x1d, 0x4f, 0xba, 0xd2, 0x98, 0x6d,
+  0x0b, 0xc7, 0xed, 0x8f, 0x26, 0x01, 0xa6, 0x66, 0x1c, 0x48, 0xf5, 0xd5,
+  0xd3, 0xdb, 0x0a, 0xad, 0xbf, 0x77, 0x44, 0x7e, 0x06, 0x33, 0x90, 0xf7,
+  0xe7, 0x28, 0xf5, 0x82, 0xc3, 0xf0, 0x43, 0x49, 0x76, 0xed, 0x73, 0x77,
+  0x5d, 0x83, 0xe9, 0x6b, 0x18, 0x01, 0xea, 0xee, 0x4f, 0x1d, 0xb2, 0x49,
+  0xe0, 0x32, 0x02, 0xd1, 0x25, 0x36, 0xdc, 0x6f, 0x28, 0x64, 0xbf, 0x89,
+  0x34, 0x83, 0x0f, 0xea, 0x03, 0x56, 0xdf, 0xb6, 0x57, 0x93, 0xbf, 0xc6,
+  0x56, 0xf7, 0xf0, 0xdd, 0x50, 0xac, 0xb6, 0x4f, 0x4d, 0x2a, 0x53, 0x11,
+  0xf1, 0xd3, 0x24, 0x2f, 0x11, 0x39, 0x0b, 0x05, 0x57, 0x1f, 0xde, 0x3d,
+  0x71, 0x1a, 0x2f, 0xb4, 0x9c, 0xf5, 0xab, 0xe3, 0xd2, 0xd1, 0xd1, 0x29,
+  0xc0, 0x36, 0x91, 0x21, 0x61, 0xe7, 0xc5, 0xd0, 0xf6, 0x40, 0xda, 0xe9,
+  0xf1, 0x4c, 0x96, 0x6c, 0xd7, 0xc9, 0x13, 0x09, 0xa5, 0x22, 0x5d, 0x06,
+  0xf7, 0x0c, 0x3d, 0x65, 0xa3, 0xfc, 0x52, 0xb9, 0x7e, 0x72, 0xbd, 0x33,
+  0x7b, 0xdd, 0x7c, 0xae, 0x7a, 0x2b, 0xc1, 0x4f, 0xaa, 0xfc, 0xc8, 0xf8,
+  0xc5, 0x9d, 0x25, 0x86, 0x53, 0x55, 0x74, 0xbc, 0x1e, 0xca, 0x42, 0x4a,
+  0x33, 0xe2, 0x12, 0x2d, 0xdc, 0xd2, 0x29, 0xd9, 0x7b, 0xad, 0x3a, 0x40,
+  0xec, 0x01, 0xd1, 0x05, 0xec, 0x8a, 0x9c, 0x23, 0xea, 0x86, 0x30, 0xbe,
+  0xf3, 0xe6, 0xe9, 0x08, 0xcc, 0x9e, 0xb4, 0x40, 0x8c, 0xaf, 0x02, 0xe7,
+  0x7e, 0x3f, 0x7e, 0xe2, 0xc1, 0x08, 0x02, 0xd7, 0x23, 0x29, 0x63, 0x7b,
+  0xeb, 0x82, 0x55, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0e,
+  0x30, 0x82, 0x01, 0x0a, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+  0x30, 0x60, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x59, 0x30, 0x57, 0x30,
+  0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+  0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d,
+  0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66,
+  0x6d, 0x30, 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xbd, 0x47,
+  0x0d, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82,
+  0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5,
+  0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b,
+  0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75,
+  0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72,
+  0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x33, 0xdc,
+  0x9e, 0x96, 0xec, 0xd8, 0xe8, 0x35, 0x1f, 0x6d, 0x90, 0x1b, 0x0b, 0x38,
+  0xa4, 0xaf, 0x74, 0x1b, 0xc6, 0x58, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x74, 0x76, 0xa8, 0x19, 0x77, 0x97, 0x5c, 0xaf, 0x5b, 0x8f,
+  0xe5, 0xe5, 0xec, 0xdd, 0x59, 0xbb, 0x90, 0xe6, 0x52, 0x4c, 0x1e, 0xc4,
+  0xfe, 0xda, 0x34, 0xe6, 0x2a, 0xfc, 0x3c, 0xdd, 0xb0, 0x7b, 0x69, 0x57,
+  0x82, 0xc5, 0x24, 0x26, 0x0c, 0x74, 0x1e, 0xd9, 0xa0, 0x7d, 0x7e, 0x8b,
+  0xfa, 0x76, 0xab, 0xa1, 0x7d, 0x58, 0xae, 0x34, 0x12, 0xfb, 0x99, 0x24,
+  0x14, 0x94, 0x31, 0x15, 0x62, 0xd7, 0xd9, 0x5c, 0x79, 0x33, 0x1c, 0xe2,
+  0x1b, 0x91, 0x13, 0xfd, 0xf8, 0xcf, 0x1c, 0xff, 0x2c, 0xcc, 0xb5, 0xed,
+  0x27, 0x8e, 0xb9, 0x97, 0x30, 0xb1, 0xde, 0xe9, 0xe6, 0xd2, 0x10, 0xe3,
+  0xa6, 0xd7, 0x9a, 0xf0, 0x39, 0x1a, 0x4e, 0x3b, 0xae, 0xe2, 0x0b, 0xb2,
+  0xea, 0x0d, 0x80, 0x61, 0x31, 0x33, 0xcc, 0x73, 0xf1, 0xd3, 0x0c, 0xe9,
+  0x31, 0x26, 0x9e, 0x78, 0xd9, 0x08, 0x67, 0x93, 0x71, 0xe8, 0xb6, 0xf1,
+  0xa3, 0x66, 0xfd, 0x00, 0x46, 0x0c, 0x7f, 0xda, 0xc3, 0xfc, 0x63, 0xd8,
+  0xc5, 0x3f, 0xc8, 0x23, 0x70, 0xb9, 0xa8, 0x60, 0xc2, 0x5e, 0x47, 0xa8,
+  0x8d, 0x19, 0x89, 0x63, 0xa7, 0xa0, 0x69, 0x07, 0x9c, 0x27, 0x54, 0x22,
+  0x2c, 0x4e, 0xc7, 0x3d, 0x99, 0x3c, 0xb9, 0xfc, 0x93, 0x80, 0x65, 0xbd,
+  0xbb, 0xd0, 0xf8, 0xea, 0x8c, 0xfb, 0x71, 0xe1, 0xe8, 0xa6, 0x07, 0x40,
+  0xf1, 0xd0, 0x05, 0x85, 0xda, 0x38, 0x71, 0x59, 0xf4, 0x98, 0xb7, 0x64,
+  0xd3, 0x76, 0x10, 0xf5, 0xb9, 0x6a, 0xd7, 0x15, 0x0e, 0x58, 0xfe, 0xe6,
+  0x6b, 0x29, 0x4a, 0xee, 0x75, 0x88, 0x9f, 0x5f, 0x66, 0x80, 0x07, 0x6b,
+  0x6b, 0x55, 0x68, 0xbc, 0x66, 0x39, 0xc5, 0x9f, 0x57, 0xfe, 0x35, 0xf5,
+  0x89, 0x0f, 0x07, 0x57, 0xfa, 0xd1, 0x57, 0xef, 0x4d, 0xb2, 0xd7, 0x77,
+  0xc8, 0xbc, 0xf8, 0x65, 0x0f, 0x9e,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:45:0c
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:dd:35:1d:f2:20:54:26:1a:d0:ef:a5:6f:81:76:
+                    59:70:dc:e7:f4:d4:03:24:1f:24:0e:9d:22:9f:d4:
+                    27:32:7a:2b:7c:ee:8b:e3:61:62:38:17:af:b4:4b:
+                    7a:9f:67:21:1c:2d:95:54:ba:79:ba:b6:c4:f2:0d:
+                    21:74:17:67:74:e2:b1:64:08:99:60:78:fb:67:c2:
+                    4b:f7:27:8d:6f:36:76:cf:31:8c:e5:f1:06:d7:dc:
+                    57:0e:5b:ac:ee:ce:2d:ab:aa:a9:70:2f:02:86:c8:
+                    b1:d0:08:07:95:ea:2a:ec:d1:9e:e4:36:5c:3b:a6:
+                    36:b5:43:8b:ab:f7:8e:3e:00:1b:ff:85:59:6b:62:
+                    01:8d:82:e8:4a:ba:38:b3:e0:c3:f4:6d:19:a7:ea:
+                    05:dd:84:67:c2:66:c7:24:02:73:5a:b5:ee:a4:19:
+                    d9:fc:00:ce:b6:a4:8d:df:7e:bd:5f:b2:3a:9d:84:
+                    31:4f:c8:63:0c:e4:d8:0d:52:a3:7e:01:1b:d4:67:
+                    a5:18:28:eb:01:a7:82:3c:d9:8e:1d:e5:47:0d:ba:
+                    8b:59:14:a3:1f:1f:4b:ea:e2:27:46:86:ce:9d:39:
+                    c4:66:41:a7:e2:15:23:6b:56:47:c1:ed:c5:53:e4:
+                    d4:80:1f:6b:fa:80:46:98:b2:09:a6:0f:95:be:66:
+                    88:93
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                5D:46:B2:8D:C4:4B:74:1C:BB:ED:F5:73:B6:3A:B7:38:8F:75:9E:7E
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         1b:e0:88:00:c7:05:11:1c:ff:ab:2d:48:42:52:cd:20:68:e7:
+         7c:41:8d:c1:27:c5:2c:59:67:a0:9a:35:db:b3:50:a7:1b:62:
+         e9:a5:3b:fa:b6:21:07:a7:7c:f3:0e:2b:6e:7e:2e:4d:93:9c:
+         ba:f2:86:3e:63:88:10:d8:5b:61:50:12:db:87:ae:19:bb:d2:
+         df:32:96:00:a8:5e:dc:2d:23:bc:b0:d3:b5:4a:a0:8e:65:91:
+         2f:d9:f6:82:f6:74:b2:df:7c:26:ef:19:2b:97:2f:e0:a1:ee:
+         b9:17:22:48:3f:a5:f7:0d:60:d5:0d:51:47:e5:58:fe:b7:9f:
+         8d:5e:75:3c:c6:41:f0:cf:81:54:49:11:c6:17:a4:e0:56:61:
+         dc:3d:3f:dd:67:6c:76:45:da:4a:ea:ae:1a:a4:60:4f:c7:a3:
+         d6:aa:a7:d9:cd:81:2b:c1:66:75:b2:80:8f:f5:87:4d:5f:c2:
+         5a:f5:90:c6:da:c1:bd:f4:85:a8:3c:23:2a:e1:14:7b:c1:37:
+         dd:62:d1:92:6c:ba:60:7d:88:e4:1c:b7:e4:76:51:38:c4:a9:
+         47:4e:a8:2b:2e:90:d2:b5:38:51:eb:c1:9c:8a:6a:b5:cc:b2:
+         1d:e8:c0:56:54:4c:a8:8b:f0:89:32:86:dc:93:32:be:4d:1a:
+         fa:35:75:b5
+-----BEGIN CERTIFICATE-----
+MIIEYDCCA0igAwIBAgILBAAAAAABL07hRQwwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMTMwMQYDVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBW
+YWxpZGF0aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDdNR3yIFQmGtDvpW+Bdllw3Of01AMkHyQOnSKf1Ccyeit87ovjYWI4F6+0S3qf
+ZyEcLZVUunm6tsTyDSF0F2d04rFkCJlgePtnwkv3J41vNnbPMYzl8QbX3FcOW6zu
+zi2rqqlwLwKGyLHQCAeV6irs0Z7kNlw7pja1Q4ur944+ABv/hVlrYgGNguhKujiz
+4MP0bRmn6gXdhGfCZsckAnNate6kGdn8AM62pI3ffr1fsjqdhDFPyGMM5NgNUqN+
+ARvUZ6UYKOsBp4I82Y4d5UcNuotZFKMfH0vq4idGhs6dOcRmQafiFSNrVkfB7cVT
+5NSAH2v6gEaYsgmmD5W+ZoiTAgMBAAGjggElMIIBITAOBgNVHQ8BAf8EBAMCAQYw
+EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUXUayjcRLdBy77fVztjq3OI91
+nn4wRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3
+Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0
+dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAv
+MC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEw
+HwYDVR0jBBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQAD
+ggEBABvgiADHBREc/6stSEJSzSBo53xBjcEnxSxZZ6CaNduzUKcbYumlO/q2IQen
+fPMOK25+Lk2TnLryhj5jiBDYW2FQEtuHrhm70t8ylgCoXtwtI7yw07VKoI5lkS/Z
+9oL2dLLffCbvGSuXL+Ch7rkXIkg/pfcNYNUNUUflWP63n41edTzGQfDPgVRJEcYX
+pOBWYdw9P91nbHZF2krqrhqkYE/Ho9aqp9nNgSvBZnWygI/1h01fwlr1kMbawb30
+hag8IyrhFHvBN91i0ZJsumB9iOQct+R2UTjEqUdOqCsukNK1OFHrwZyKarXMsh3o
+wFZUTKiL8IkyhtyTMr5NGvo1dbU=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert31[] = {
+  0x30, 0x82, 0x04, 0x60, 0x30, 0x82, 0x03, 0x48, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x45, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x5d, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+  0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+  0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x47,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72,
+  0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41,
+  0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xdd, 0x35, 0x1d, 0xf2, 0x20, 0x54, 0x26, 0x1a, 0xd0, 0xef,
+  0xa5, 0x6f, 0x81, 0x76, 0x59, 0x70, 0xdc, 0xe7, 0xf4, 0xd4, 0x03, 0x24,
+  0x1f, 0x24, 0x0e, 0x9d, 0x22, 0x9f, 0xd4, 0x27, 0x32, 0x7a, 0x2b, 0x7c,
+  0xee, 0x8b, 0xe3, 0x61, 0x62, 0x38, 0x17, 0xaf, 0xb4, 0x4b, 0x7a, 0x9f,
+  0x67, 0x21, 0x1c, 0x2d, 0x95, 0x54, 0xba, 0x79, 0xba, 0xb6, 0xc4, 0xf2,
+  0x0d, 0x21, 0x74, 0x17, 0x67, 0x74, 0xe2, 0xb1, 0x64, 0x08, 0x99, 0x60,
+  0x78, 0xfb, 0x67, 0xc2, 0x4b, 0xf7, 0x27, 0x8d, 0x6f, 0x36, 0x76, 0xcf,
+  0x31, 0x8c, 0xe5, 0xf1, 0x06, 0xd7, 0xdc, 0x57, 0x0e, 0x5b, 0xac, 0xee,
+  0xce, 0x2d, 0xab, 0xaa, 0xa9, 0x70, 0x2f, 0x02, 0x86, 0xc8, 0xb1, 0xd0,
+  0x08, 0x07, 0x95, 0xea, 0x2a, 0xec, 0xd1, 0x9e, 0xe4, 0x36, 0x5c, 0x3b,
+  0xa6, 0x36, 0xb5, 0x43, 0x8b, 0xab, 0xf7, 0x8e, 0x3e, 0x00, 0x1b, 0xff,
+  0x85, 0x59, 0x6b, 0x62, 0x01, 0x8d, 0x82, 0xe8, 0x4a, 0xba, 0x38, 0xb3,
+  0xe0, 0xc3, 0xf4, 0x6d, 0x19, 0xa7, 0xea, 0x05, 0xdd, 0x84, 0x67, 0xc2,
+  0x66, 0xc7, 0x24, 0x02, 0x73, 0x5a, 0xb5, 0xee, 0xa4, 0x19, 0xd9, 0xfc,
+  0x00, 0xce, 0xb6, 0xa4, 0x8d, 0xdf, 0x7e, 0xbd, 0x5f, 0xb2, 0x3a, 0x9d,
+  0x84, 0x31, 0x4f, 0xc8, 0x63, 0x0c, 0xe4, 0xd8, 0x0d, 0x52, 0xa3, 0x7e,
+  0x01, 0x1b, 0xd4, 0x67, 0xa5, 0x18, 0x28, 0xeb, 0x01, 0xa7, 0x82, 0x3c,
+  0xd9, 0x8e, 0x1d, 0xe5, 0x47, 0x0d, 0xba, 0x8b, 0x59, 0x14, 0xa3, 0x1f,
+  0x1f, 0x4b, 0xea, 0xe2, 0x27, 0x46, 0x86, 0xce, 0x9d, 0x39, 0xc4, 0x66,
+  0x41, 0xa7, 0xe2, 0x15, 0x23, 0x6b, 0x56, 0x47, 0xc1, 0xed, 0xc5, 0x53,
+  0xe4, 0xd4, 0x80, 0x1f, 0x6b, 0xfa, 0x80, 0x46, 0x98, 0xb2, 0x09, 0xa6,
+  0x0f, 0x95, 0xbe, 0x66, 0x88, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x25, 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0x46, 0xb2, 0x8d, 0xc4, 0x4b,
+  0x74, 0x1c, 0xbb, 0xed, 0xf5, 0x73, 0xb6, 0x3a, 0xb7, 0x38, 0x8f, 0x75,
+  0x9e, 0x7e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30,
+  0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30,
+  0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+  0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+  0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+  0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+  0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d,
+  0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x1b, 0xe0, 0x88, 0x00, 0xc7, 0x05, 0x11, 0x1c,
+  0xff, 0xab, 0x2d, 0x48, 0x42, 0x52, 0xcd, 0x20, 0x68, 0xe7, 0x7c, 0x41,
+  0x8d, 0xc1, 0x27, 0xc5, 0x2c, 0x59, 0x67, 0xa0, 0x9a, 0x35, 0xdb, 0xb3,
+  0x50, 0xa7, 0x1b, 0x62, 0xe9, 0xa5, 0x3b, 0xfa, 0xb6, 0x21, 0x07, 0xa7,
+  0x7c, 0xf3, 0x0e, 0x2b, 0x6e, 0x7e, 0x2e, 0x4d, 0x93, 0x9c, 0xba, 0xf2,
+  0x86, 0x3e, 0x63, 0x88, 0x10, 0xd8, 0x5b, 0x61, 0x50, 0x12, 0xdb, 0x87,
+  0xae, 0x19, 0xbb, 0xd2, 0xdf, 0x32, 0x96, 0x00, 0xa8, 0x5e, 0xdc, 0x2d,
+  0x23, 0xbc, 0xb0, 0xd3, 0xb5, 0x4a, 0xa0, 0x8e, 0x65, 0x91, 0x2f, 0xd9,
+  0xf6, 0x82, 0xf6, 0x74, 0xb2, 0xdf, 0x7c, 0x26, 0xef, 0x19, 0x2b, 0x97,
+  0x2f, 0xe0, 0xa1, 0xee, 0xb9, 0x17, 0x22, 0x48, 0x3f, 0xa5, 0xf7, 0x0d,
+  0x60, 0xd5, 0x0d, 0x51, 0x47, 0xe5, 0x58, 0xfe, 0xb7, 0x9f, 0x8d, 0x5e,
+  0x75, 0x3c, 0xc6, 0x41, 0xf0, 0xcf, 0x81, 0x54, 0x49, 0x11, 0xc6, 0x17,
+  0xa4, 0xe0, 0x56, 0x61, 0xdc, 0x3d, 0x3f, 0xdd, 0x67, 0x6c, 0x76, 0x45,
+  0xda, 0x4a, 0xea, 0xae, 0x1a, 0xa4, 0x60, 0x4f, 0xc7, 0xa3, 0xd6, 0xaa,
+  0xa7, 0xd9, 0xcd, 0x81, 0x2b, 0xc1, 0x66, 0x75, 0xb2, 0x80, 0x8f, 0xf5,
+  0x87, 0x4d, 0x5f, 0xc2, 0x5a, 0xf5, 0x90, 0xc6, 0xda, 0xc1, 0xbd, 0xf4,
+  0x85, 0xa8, 0x3c, 0x23, 0x2a, 0xe1, 0x14, 0x7b, 0xc1, 0x37, 0xdd, 0x62,
+  0xd1, 0x92, 0x6c, 0xba, 0x60, 0x7d, 0x88, 0xe4, 0x1c, 0xb7, 0xe4, 0x76,
+  0x51, 0x38, 0xc4, 0xa9, 0x47, 0x4e, 0xa8, 0x2b, 0x2e, 0x90, 0xd2, 0xb5,
+  0x38, 0x51, 0xeb, 0xc1, 0x9c, 0x8a, 0x6a, 0xb5, 0xcc, 0xb2, 0x1d, 0xe8,
+  0xc0, 0x56, 0x54, 0x4c, 0xa8, 0x8b, 0xf0, 0x89, 0x32, 0x86, 0xdc, 0x93,
+  0x32, 0xbe, 0x4d, 0x1a, 0xfa, 0x35, 0x75, 0xb5,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:1e:44:a5:f5:2a
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 11 12:00:00 2007 GMT
+            Not After : Apr 11 12:00:00 2017 GMT
+        Subject: OU=Organization Validation CA, O=GlobalSign, CN=GlobalSign Organization Validation CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a1:2f:c4:bc:ce:87:03:e9:67:c1:89:c8:e5:93:
+                    fc:7d:b4:ad:9e:f6:63:4e:6a:e8:9c:2c:73:89:a2:
+                    01:f4:8f:21:f8:fd:25:9d:58:16:6d:86:f6:ee:49:
+                    57:75:7e:75:ea:22:11:7e:3d:fb:c7:42:41:dc:fc:
+                    c5:0c:91:55:80:7b:eb:64:33:1d:9b:f9:ca:38:e9:
+                    ab:c6:25:43:51:25:40:f4:e4:7e:18:55:6a:a9:8f:
+                    10:3a:40:1e:d6:57:83:ef:7f:2f:34:2f:2d:d2:f6:
+                    53:c2:19:0d:b7:ed:c9:81:f5:46:2c:b4:23:42:5e:
+                    9d:13:03:75:ec:ea:6a:fc:57:7c:c9:36:97:3b:98:
+                    dc:13:13:ec:ec:41:fa:5d:34:ea:b9:93:e7:10:16:
+                    65:cc:9c:92:fd:f5:c5:9d:3e:4a:b9:09:fc:e4:5f:
+                    1e:69:5f:4d:f4:56:72:44:b1:1d:23:03:c8:36:f6:
+                    65:88:c8:bf:39:16:45:8e:1e:26:6c:51:16:c5:2a:
+                    00:38:c5:a4:13:69:95:7d:ab:01:3b:a8:c4:14:b4:
+                    80:da:ac:1a:44:20:d5:fe:a9:06:7b:14:27:af:e0:
+                    30:21:dd:90:f4:a9:d5:23:19:2e:1e:03:e6:c1:df:
+                    95:29:e4:c1:94:43:dd:3e:90:aa:cb:4b:c9:be:8a:
+                    d3:39
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                7D:6D:2A:EC:66:AB:A7:51:36:AB:02:69:F1:70:8F:C4:59:0B:9A:1F
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.4146.1.20
+                  CPS: http://www.globalsign.net/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Netscape Cert Type: 
+                SSL CA
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         79:47:fc:15:d7:4c:79:df:0f:7a:9e:ce:d4:7c:4b:63:c9:89:
+         b5:7b:3f:99:12:e8:9c:8c:9a:49:2f:e0:4e:95:4a:ed:c7:bc:
+         be:f1:a2:db:8e:93:1d:ba:71:54:aa:4b:d9:89:22:24:87:c5:
+         04:a8:ac:82:52:a0:52:f8:b8:e1:4f:a1:27:66:63:21:4a:39:
+         e7:c7:c5:4e:5f:b2:d6:1d:13:6d:30:e9:ce:d7:a2:1c:bc:29:
+         0a:73:3c:5b:23:49:fe:d6:ff:ca:b0:4f:f5:f2:67:98:c0:47:
+         11:f8:b7:48:a6:90:09:d6:42:be:ea:b1:b9:53:42:c3:9c:20:
+         c9:fb:a1:5b:b5:56:6d:87:81:c8:60:ac:c4:b9:72:27:0a:8e:
+         1e:a8:b1:2e:cd:32:a2:78:57:b0:9c:f8:95:bb:43:8e:8c:31:
+         86:6e:53:0d:c6:12:05:ba:41:6e:a8:35:30:09:18:1d:02:61:
+         ff:fd:ee:35:de:6a:c3:3b:d0:4d:4b:4e:50:b2:56:36:0c:44:
+         5d:da:1a:65:2a:e6:98:56:a9:63:33:2e:04:e7:ae:e8:f4:8e:
+         b7:b2:da:7d:c0:c8:e2:ae:a6:28:2f:e3:c9:73:bd:fc:07:41:
+         34:b7:aa:6e:ee:a7:db:d1:93:3c:ed:90:ec:32:92:88:d9:c8:
+         23:6c:74:21
+-----BEGIN CERTIFICATE-----
+MIIEZzCCA0+gAwIBAgILBAAAAAABHkSl9SowDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNzA0MTExMjAw
+MDBaFw0xNzA0MTExMjAwMDBaMGoxIzAhBgNVBAsTGk9yZ2FuaXphdGlvbiBWYWxp
+ZGF0aW9uIENBMRMwEQYDVQQKEwpHbG9iYWxTaWduMS4wLAYDVQQDEyVHbG9iYWxT
+aWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAoS/EvM6HA+lnwYnI5ZP8fbStnvZjTmronCxziaIB9I8h
++P0lnVgWbYb27klXdX516iIRfj37x0JB3PzFDJFVgHvrZDMdm/nKOOmrxiVDUSVA
+9OR+GFVqqY8QOkAe1leD738vNC8t0vZTwhkNt+3JgfVGLLQjQl6dEwN17Opq/Fd8
+yTaXO5jcExPs7EH6XTTquZPnEBZlzJyS/fXFnT5KuQn85F8eaV9N9FZyRLEdIwPI
+NvZliMi/ORZFjh4mbFEWxSoAOMWkE2mVfasBO6jEFLSA2qwaRCDV/qkGexQnr+Aw
+Id2Q9KnVIxkuHgPmwd+VKeTBlEPdPpCqy0vJvorTOQIDAQABo4IBHzCCARswDgYD
+VR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFH1tKuxm
+q6dRNqsCafFwj8RZC5ofMEsGA1UdIAREMEIwQAYJKwYBBAGgMgEUMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2xvYmFsc2lnbi5uZXQvcmVwb3NpdG9yeS8wMwYD
+VR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNy
+bDARBglghkgBhvhCAQEEBAMCAgQwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI
+AYb4QgQBMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3
+DQEBBQUAA4IBAQB5R/wV10x53w96ns7UfEtjyYm1ez+ZEuicjJpJL+BOlUrtx7y+
+8aLbjpMdunFUqkvZiSIkh8UEqKyCUqBS+LjhT6EnZmMhSjnnx8VOX7LWHRNtMOnO
+16IcvCkKczxbI0n+1v/KsE/18meYwEcR+LdIppAJ1kK+6rG5U0LDnCDJ+6FbtVZt
+h4HIYKzEuXInCo4eqLEuzTKieFewnPiVu0OOjDGGblMNxhIFukFuqDUwCRgdAmH/
+/e413mrDO9BNS05QslY2DERd2hplKuaYVqljMy4E567o9I63stp9wMjirqYoL+PJ
+c738B0E0t6pu7qfb0ZM87ZDsMpKI2cgjbHQh
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert32[] = {
+  0x30, 0x82, 0x04, 0x67, 0x30, 0x82, 0x03, 0x4f, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1e, 0x44, 0xa5,
+  0xf5, 0x2a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31, 0x32, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31,
+  0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6a, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1a, 0x4f, 0x72, 0x67, 0x61, 0x6e,
+  0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69,
+  0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x25, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+  0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+  0x01, 0x01, 0x00, 0xa1, 0x2f, 0xc4, 0xbc, 0xce, 0x87, 0x03, 0xe9, 0x67,
+  0xc1, 0x89, 0xc8, 0xe5, 0x93, 0xfc, 0x7d, 0xb4, 0xad, 0x9e, 0xf6, 0x63,
+  0x4e, 0x6a, 0xe8, 0x9c, 0x2c, 0x73, 0x89, 0xa2, 0x01, 0xf4, 0x8f, 0x21,
+  0xf8, 0xfd, 0x25, 0x9d, 0x58, 0x16, 0x6d, 0x86, 0xf6, 0xee, 0x49, 0x57,
+  0x75, 0x7e, 0x75, 0xea, 0x22, 0x11, 0x7e, 0x3d, 0xfb, 0xc7, 0x42, 0x41,
+  0xdc, 0xfc, 0xc5, 0x0c, 0x91, 0x55, 0x80, 0x7b, 0xeb, 0x64, 0x33, 0x1d,
+  0x9b, 0xf9, 0xca, 0x38, 0xe9, 0xab, 0xc6, 0x25, 0x43, 0x51, 0x25, 0x40,
+  0xf4, 0xe4, 0x7e, 0x18, 0x55, 0x6a, 0xa9, 0x8f, 0x10, 0x3a, 0x40, 0x1e,
+  0xd6, 0x57, 0x83, 0xef, 0x7f, 0x2f, 0x34, 0x2f, 0x2d, 0xd2, 0xf6, 0x53,
+  0xc2, 0x19, 0x0d, 0xb7, 0xed, 0xc9, 0x81, 0xf5, 0x46, 0x2c, 0xb4, 0x23,
+  0x42, 0x5e, 0x9d, 0x13, 0x03, 0x75, 0xec, 0xea, 0x6a, 0xfc, 0x57, 0x7c,
+  0xc9, 0x36, 0x97, 0x3b, 0x98, 0xdc, 0x13, 0x13, 0xec, 0xec, 0x41, 0xfa,
+  0x5d, 0x34, 0xea, 0xb9, 0x93, 0xe7, 0x10, 0x16, 0x65, 0xcc, 0x9c, 0x92,
+  0xfd, 0xf5, 0xc5, 0x9d, 0x3e, 0x4a, 0xb9, 0x09, 0xfc, 0xe4, 0x5f, 0x1e,
+  0x69, 0x5f, 0x4d, 0xf4, 0x56, 0x72, 0x44, 0xb1, 0x1d, 0x23, 0x03, 0xc8,
+  0x36, 0xf6, 0x65, 0x88, 0xc8, 0xbf, 0x39, 0x16, 0x45, 0x8e, 0x1e, 0x26,
+  0x6c, 0x51, 0x16, 0xc5, 0x2a, 0x00, 0x38, 0xc5, 0xa4, 0x13, 0x69, 0x95,
+  0x7d, 0xab, 0x01, 0x3b, 0xa8, 0xc4, 0x14, 0xb4, 0x80, 0xda, 0xac, 0x1a,
+  0x44, 0x20, 0xd5, 0xfe, 0xa9, 0x06, 0x7b, 0x14, 0x27, 0xaf, 0xe0, 0x30,
+  0x21, 0xdd, 0x90, 0xf4, 0xa9, 0xd5, 0x23, 0x19, 0x2e, 0x1e, 0x03, 0xe6,
+  0xc1, 0xdf, 0x95, 0x29, 0xe4, 0xc1, 0x94, 0x43, 0xdd, 0x3e, 0x90, 0xaa,
+  0xcb, 0x4b, 0xc9, 0xbe, 0x8a, 0xd3, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01,
+  0xa3, 0x82, 0x01, 0x1f, 0x30, 0x82, 0x01, 0x1b, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x7d, 0x6d, 0x2a, 0xec, 0x66,
+  0xab, 0xa7, 0x51, 0x36, 0xab, 0x02, 0x69, 0xf1, 0x70, 0x8f, 0xc4, 0x59,
+  0x0b, 0x9a, 0x1f, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x44,
+  0x30, 0x42, 0x30, 0x40, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xa0,
+  0x32, 0x01, 0x14, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x70,
+  0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0,
+  0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+  0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45,
+  0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff,
+  0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x79,
+  0x47, 0xfc, 0x15, 0xd7, 0x4c, 0x79, 0xdf, 0x0f, 0x7a, 0x9e, 0xce, 0xd4,
+  0x7c, 0x4b, 0x63, 0xc9, 0x89, 0xb5, 0x7b, 0x3f, 0x99, 0x12, 0xe8, 0x9c,
+  0x8c, 0x9a, 0x49, 0x2f, 0xe0, 0x4e, 0x95, 0x4a, 0xed, 0xc7, 0xbc, 0xbe,
+  0xf1, 0xa2, 0xdb, 0x8e, 0x93, 0x1d, 0xba, 0x71, 0x54, 0xaa, 0x4b, 0xd9,
+  0x89, 0x22, 0x24, 0x87, 0xc5, 0x04, 0xa8, 0xac, 0x82, 0x52, 0xa0, 0x52,
+  0xf8, 0xb8, 0xe1, 0x4f, 0xa1, 0x27, 0x66, 0x63, 0x21, 0x4a, 0x39, 0xe7,
+  0xc7, 0xc5, 0x4e, 0x5f, 0xb2, 0xd6, 0x1d, 0x13, 0x6d, 0x30, 0xe9, 0xce,
+  0xd7, 0xa2, 0x1c, 0xbc, 0x29, 0x0a, 0x73, 0x3c, 0x5b, 0x23, 0x49, 0xfe,
+  0xd6, 0xff, 0xca, 0xb0, 0x4f, 0xf5, 0xf2, 0x67, 0x98, 0xc0, 0x47, 0x11,
+  0xf8, 0xb7, 0x48, 0xa6, 0x90, 0x09, 0xd6, 0x42, 0xbe, 0xea, 0xb1, 0xb9,
+  0x53, 0x42, 0xc3, 0x9c, 0x20, 0xc9, 0xfb, 0xa1, 0x5b, 0xb5, 0x56, 0x6d,
+  0x87, 0x81, 0xc8, 0x60, 0xac, 0xc4, 0xb9, 0x72, 0x27, 0x0a, 0x8e, 0x1e,
+  0xa8, 0xb1, 0x2e, 0xcd, 0x32, 0xa2, 0x78, 0x57, 0xb0, 0x9c, 0xf8, 0x95,
+  0xbb, 0x43, 0x8e, 0x8c, 0x31, 0x86, 0x6e, 0x53, 0x0d, 0xc6, 0x12, 0x05,
+  0xba, 0x41, 0x6e, 0xa8, 0x35, 0x30, 0x09, 0x18, 0x1d, 0x02, 0x61, 0xff,
+  0xfd, 0xee, 0x35, 0xde, 0x6a, 0xc3, 0x3b, 0xd0, 0x4d, 0x4b, 0x4e, 0x50,
+  0xb2, 0x56, 0x36, 0x0c, 0x44, 0x5d, 0xda, 0x1a, 0x65, 0x2a, 0xe6, 0x98,
+  0x56, 0xa9, 0x63, 0x33, 0x2e, 0x04, 0xe7, 0xae, 0xe8, 0xf4, 0x8e, 0xb7,
+  0xb2, 0xda, 0x7d, 0xc0, 0xc8, 0xe2, 0xae, 0xa6, 0x28, 0x2f, 0xe3, 0xc9,
+  0x73, 0xbd, 0xfc, 0x07, 0x41, 0x34, 0xb7, 0xaa, 0x6e, 0xee, 0xa7, 0xdb,
+  0xd1, 0x93, 0x3c, 0xed, 0x90, 0xec, 0x32, 0x92, 0x88, 0xd9, 0xc8, 0x23,
+  0x6c, 0x74, 0x21,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1200005093 (0x47869fe5)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=SecureTrust Corporation, CN=SecureTrust CA
+        Validity
+            Not Before: Dec 22 23:47:39 2008 GMT
+            Not After : Dec 22 23:47:39 2028 GMT
+        Subject: C=US, ST=Illinois, L=Chicago, O=Trustwave Holdings, Inc., CN=Trustwave Organization Validation CA, Level 2/emailAddress=ca@trustwave.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e8:14:ee:a0:da:97:bd:89:bd:0c:cc:8d:df:08:
+                    fb:06:09:83:a8:23:51:5b:01:3f:34:da:1f:1f:6e:
+                    c1:e3:58:65:cb:0a:f9:f3:87:c8:c8:fa:a1:cc:f5:
+                    74:e2:b8:ae:2d:06:84:80:28:6f:5a:c1:22:5c:92:
+                    94:42:cd:19:02:12:5c:10:62:7e:a2:44:fb:16:5e:
+                    9c:72:b1:ac:ea:04:e6:15:aa:99:e8:5a:f8:58:b9:
+                    87:24:e8:75:cd:25:88:e2:58:92:5e:86:83:7f:8a:
+                    23:53:ae:8a:e8:a3:21:7e:83:af:40:09:18:49:af:
+                    e1:d0:5a:b0:4f:6f:e2:31:ad:f4:f1:37:1f:c9:2a:
+                    e1:8b:d6:8c:12:31:d4:27:1a:df:ea:6b:9e:78:53:
+                    ed:9a:19:b0:ce:45:44:5b:1b:ef:64:59:21:fa:c7:
+                    b7:d1:d3:0c:1e:cb:88:da:fd:23:3f:f4:ac:2b:a0:
+                    4d:61:d3:be:ca:de:19:61:61:24:f1:f6:9c:b4:96:
+                    bd:9d:eb:17:9f:24:39:78:e9:23:50:d3:01:50:77:
+                    d8:52:64:2f:3e:19:4f:75:b9:17:b1:da:8d:e0:d0:
+                    ed:db:37:13:dc:2f:e0:5f:80:68:d7:f4:87:ba:c1:
+                    1f:12:78:d0:08:27:17:7a:98:a6:9f:d2:21:ba:4e:
+                    87:bf
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                5D:D9:96:9A:40:C7:27:CB:2C:9B:A2:EC:CF:19:AB:C8:AF:CC:86:48
+            X509v3 Authority Key Identifier: 
+                keyid:42:32:B6:16:FA:04:FD:FE:5D:4B:7A:C3:FD:F7:4C:40:1D:5A:43:AF
+
+            X509v3 Key Usage: 
+                Certificate Sign, CRL Sign
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.securetrust.com/STCA.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.30360.3.0
+                Policy: 1.3.6.1.4.1.30360.3.3.3.3.4.4.3
+                  CPS: http://www.securetrust.com/legal/
+
+    Signature Algorithm: sha1WithRSAEncryption
+         53:f1:c8:a0:17:ec:6c:88:82:b1:c0:24:af:d1:08:58:b3:2c:
+         6f:7b:c1:5c:89:92:6f:88:fc:4b:c0:02:50:93:2f:5a:41:98:
+         59:b6:e3:7f:8c:14:63:77:7d:45:3c:88:50:5e:a6:81:52:00:
+         c8:c5:fe:48:ee:1f:5d:ad:de:44:0b:42:58:9c:e1:67:5c:43:
+         b6:a0:85:98:ff:16:d4:1a:28:be:76:e1:2f:e1:84:f4:7e:b9:
+         27:aa:77:cb:36:b3:fe:c3:fa:d2:17:f6:e1:62:4e:d3:cc:cc:
+         b3:19:65:d3:4b:a8:e8:b3:d5:4c:ea:f6:4e:ae:cb:ae:34:48:
+         1f:60:cc:58:e7:e7:74:c9:01:35:fd:6a:e0:58:8a:d2:16:eb:
+         ec:e9:3e:bb:f0:1d:cf:b6:ff:1e:0c:b7:bb:39:e9:b7:98:1b:
+         c0:52:21:eb:3a:3d:78:38:8c:a9:19:5f:27:a4:d0:7f:36:61:
+         ab:24:7e:9f:f8:2d:3f:92:29:63:be:cb:10:db:0d:40:36:02:
+         a0:d4:17:a2:8d:7f:7e:7c:99:af:45:5a:40:cd:a2:6b:5c:be:
+         0e:f3:d3:87:fc:a1:10:ca:aa:33:b7:ba:4b:c0:3d:a4:21:8c:
+         17:9c:cf:d8:bf:e6:57:fe:cd:eb:fa:30:1a:d5:fe:e8:25:97:
+         a9:be:3b:ea
+-----BEGIN CERTIFICATE-----
+MIIEajCCA1KgAwIBAgIER4af5TANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQGEwJV
+UzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNl
+Y3VyZVRydXN0IENBMB4XDTA4MTIyMjIzNDczOVoXDTI4MTIyMjIzNDczOVowga4x
+CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczEQMA4GA1UEBxMHQ2hpY2Fn
+bzEhMB8GA1UEChMYVHJ1c3R3YXZlIEhvbGRpbmdzLCBJbmMuMTYwNAYDVQQDEy1U
+cnVzdHdhdmUgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gQ0EsIExldmVsIDIxHzAd
+BgkqhkiG9w0BCQEWEGNhQHRydXN0d2F2ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDoFO6g2pe9ib0MzI3fCPsGCYOoI1FbAT802h8fbsHjWGXL
+Cvnzh8jI+qHM9XTiuK4tBoSAKG9awSJckpRCzRkCElwQYn6iRPsWXpxysazqBOYV
+qpnoWvhYuYck6HXNJYjiWJJehoN/iiNTrorooyF+g69ACRhJr+HQWrBPb+IxrfTx
+Nx/JKuGL1owSMdQnGt/qa554U+2aGbDORURbG+9kWSH6x7fR0wwey4ja/SM/9Kwr
+oE1h077K3hlhYSTx9py0lr2d6xefJDl46SNQ0wFQd9hSZC8+GU91uRex2o3g0O3b
+NxPcL+BfgGjX9Ie6wR8SeNAIJxd6mKaf0iG6Toe/AgMBAAGjgfQwgfEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUXdmWmkDHJ8ssm6LszxmryK/MhkgwHwYDVR0j
+BBgwFoAUQjK2FvoE/f5dS3rD/fdMQB1aQ68wCwYDVR0PBAQDAgEGMDQGA1UdHwQt
+MCswKaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NUQ0EuY3JsMFsG
+A1UdIARUMFIwDAYKKwYBBAGB7RgDADBCBg8rBgEEAYHtGAMDAwMEBAMwLzAtBggr
+BgEFBQcCARYhaHR0cDovL3d3dy5zZWN1cmV0cnVzdC5jb20vbGVnYWwvMA0GCSqG
+SIb3DQEBBQUAA4IBAQBT8cigF+xsiIKxwCSv0QhYsyxve8FciZJviPxLwAJQky9a
+QZhZtuN/jBRjd31FPIhQXqaBUgDIxf5I7h9drd5EC0JYnOFnXEO2oIWY/xbUGii+
+duEv4YT0frknqnfLNrP+w/rSF/bhYk7TzMyzGWXTS6jos9VM6vZOrsuuNEgfYMxY
+5+d0yQE1/WrgWIrSFuvs6T678B3Ptv8eDLe7Oem3mBvAUiHrOj14OIypGV8npNB/
+NmGrJH6f+C0/kiljvssQ2w1ANgKg1BeijX9+fJmvRVpAzaJrXL4O89OH/KEQyqoz
+t7pLwD2kIYwXnM/Yv+ZX/s3r+jAa1f7oJZepvjvq
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert33[] = {
+  0x30, 0x82, 0x04, 0x6a, 0x30, 0x82, 0x03, 0x52, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x47, 0x86, 0x9f, 0xe5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x48,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x17,
+  0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x53, 0x65,
+  0x63, 0x75, 0x72, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x41,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x31, 0x32, 0x32, 0x32, 0x32, 0x33,
+  0x34, 0x37, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x32, 0x32,
+  0x32, 0x32, 0x33, 0x34, 0x37, 0x33, 0x39, 0x5a, 0x30, 0x81, 0xae, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x08, 0x49,
+  0x6c, 0x6c, 0x69, 0x6e, 0x6f, 0x69, 0x73, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x43, 0x68, 0x69, 0x63, 0x61, 0x67,
+  0x6f, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76, 0x65, 0x20, 0x48, 0x6f,
+  0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76, 0x65, 0x20, 0x4f, 0x72, 0x67,
+  0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61,
+  0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x2c,
+  0x20, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x32, 0x31, 0x1f, 0x30, 0x1d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+  0x10, 0x63, 0x61, 0x40, 0x74, 0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76,
+  0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xe8, 0x14, 0xee, 0xa0, 0xda, 0x97, 0xbd, 0x89, 0xbd, 0x0c,
+  0xcc, 0x8d, 0xdf, 0x08, 0xfb, 0x06, 0x09, 0x83, 0xa8, 0x23, 0x51, 0x5b,
+  0x01, 0x3f, 0x34, 0xda, 0x1f, 0x1f, 0x6e, 0xc1, 0xe3, 0x58, 0x65, 0xcb,
+  0x0a, 0xf9, 0xf3, 0x87, 0xc8, 0xc8, 0xfa, 0xa1, 0xcc, 0xf5, 0x74, 0xe2,
+  0xb8, 0xae, 0x2d, 0x06, 0x84, 0x80, 0x28, 0x6f, 0x5a, 0xc1, 0x22, 0x5c,
+  0x92, 0x94, 0x42, 0xcd, 0x19, 0x02, 0x12, 0x5c, 0x10, 0x62, 0x7e, 0xa2,
+  0x44, 0xfb, 0x16, 0x5e, 0x9c, 0x72, 0xb1, 0xac, 0xea, 0x04, 0xe6, 0x15,
+  0xaa, 0x99, 0xe8, 0x5a, 0xf8, 0x58, 0xb9, 0x87, 0x24, 0xe8, 0x75, 0xcd,
+  0x25, 0x88, 0xe2, 0x58, 0x92, 0x5e, 0x86, 0x83, 0x7f, 0x8a, 0x23, 0x53,
+  0xae, 0x8a, 0xe8, 0xa3, 0x21, 0x7e, 0x83, 0xaf, 0x40, 0x09, 0x18, 0x49,
+  0xaf, 0xe1, 0xd0, 0x5a, 0xb0, 0x4f, 0x6f, 0xe2, 0x31, 0xad, 0xf4, 0xf1,
+  0x37, 0x1f, 0xc9, 0x2a, 0xe1, 0x8b, 0xd6, 0x8c, 0x12, 0x31, 0xd4, 0x27,
+  0x1a, 0xdf, 0xea, 0x6b, 0x9e, 0x78, 0x53, 0xed, 0x9a, 0x19, 0xb0, 0xce,
+  0x45, 0x44, 0x5b, 0x1b, 0xef, 0x64, 0x59, 0x21, 0xfa, 0xc7, 0xb7, 0xd1,
+  0xd3, 0x0c, 0x1e, 0xcb, 0x88, 0xda, 0xfd, 0x23, 0x3f, 0xf4, 0xac, 0x2b,
+  0xa0, 0x4d, 0x61, 0xd3, 0xbe, 0xca, 0xde, 0x19, 0x61, 0x61, 0x24, 0xf1,
+  0xf6, 0x9c, 0xb4, 0x96, 0xbd, 0x9d, 0xeb, 0x17, 0x9f, 0x24, 0x39, 0x78,
+  0xe9, 0x23, 0x50, 0xd3, 0x01, 0x50, 0x77, 0xd8, 0x52, 0x64, 0x2f, 0x3e,
+  0x19, 0x4f, 0x75, 0xb9, 0x17, 0xb1, 0xda, 0x8d, 0xe0, 0xd0, 0xed, 0xdb,
+  0x37, 0x13, 0xdc, 0x2f, 0xe0, 0x5f, 0x80, 0x68, 0xd7, 0xf4, 0x87, 0xba,
+  0xc1, 0x1f, 0x12, 0x78, 0xd0, 0x08, 0x27, 0x17, 0x7a, 0x98, 0xa6, 0x9f,
+  0xd2, 0x21, 0xba, 0x4e, 0x87, 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x81, 0xf4, 0x30, 0x81, 0xf1, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0xd9, 0x96,
+  0x9a, 0x40, 0xc7, 0x27, 0xcb, 0x2c, 0x9b, 0xa2, 0xec, 0xcf, 0x19, 0xab,
+  0xc8, 0xaf, 0xcc, 0x86, 0x48, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x42, 0x32, 0xb6, 0x16, 0xfa, 0x04,
+  0xfd, 0xfe, 0x5d, 0x4b, 0x7a, 0xc3, 0xfd, 0xf7, 0x4c, 0x40, 0x1d, 0x5a,
+  0x43, 0xaf, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d,
+  0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x53, 0x54, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x5b, 0x06,
+  0x03, 0x55, 0x1d, 0x20, 0x04, 0x54, 0x30, 0x52, 0x30, 0x0c, 0x06, 0x0a,
+  0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xed, 0x18, 0x03, 0x00, 0x30, 0x42,
+  0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xed, 0x18, 0x03, 0x03,
+  0x03, 0x03, 0x04, 0x04, 0x03, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x21, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x53, 0xf1, 0xc8, 0xa0, 0x17, 0xec, 0x6c, 0x88, 0x82, 0xb1,
+  0xc0, 0x24, 0xaf, 0xd1, 0x08, 0x58, 0xb3, 0x2c, 0x6f, 0x7b, 0xc1, 0x5c,
+  0x89, 0x92, 0x6f, 0x88, 0xfc, 0x4b, 0xc0, 0x02, 0x50, 0x93, 0x2f, 0x5a,
+  0x41, 0x98, 0x59, 0xb6, 0xe3, 0x7f, 0x8c, 0x14, 0x63, 0x77, 0x7d, 0x45,
+  0x3c, 0x88, 0x50, 0x5e, 0xa6, 0x81, 0x52, 0x00, 0xc8, 0xc5, 0xfe, 0x48,
+  0xee, 0x1f, 0x5d, 0xad, 0xde, 0x44, 0x0b, 0x42, 0x58, 0x9c, 0xe1, 0x67,
+  0x5c, 0x43, 0xb6, 0xa0, 0x85, 0x98, 0xff, 0x16, 0xd4, 0x1a, 0x28, 0xbe,
+  0x76, 0xe1, 0x2f, 0xe1, 0x84, 0xf4, 0x7e, 0xb9, 0x27, 0xaa, 0x77, 0xcb,
+  0x36, 0xb3, 0xfe, 0xc3, 0xfa, 0xd2, 0x17, 0xf6, 0xe1, 0x62, 0x4e, 0xd3,
+  0xcc, 0xcc, 0xb3, 0x19, 0x65, 0xd3, 0x4b, 0xa8, 0xe8, 0xb3, 0xd5, 0x4c,
+  0xea, 0xf6, 0x4e, 0xae, 0xcb, 0xae, 0x34, 0x48, 0x1f, 0x60, 0xcc, 0x58,
+  0xe7, 0xe7, 0x74, 0xc9, 0x01, 0x35, 0xfd, 0x6a, 0xe0, 0x58, 0x8a, 0xd2,
+  0x16, 0xeb, 0xec, 0xe9, 0x3e, 0xbb, 0xf0, 0x1d, 0xcf, 0xb6, 0xff, 0x1e,
+  0x0c, 0xb7, 0xbb, 0x39, 0xe9, 0xb7, 0x98, 0x1b, 0xc0, 0x52, 0x21, 0xeb,
+  0x3a, 0x3d, 0x78, 0x38, 0x8c, 0xa9, 0x19, 0x5f, 0x27, 0xa4, 0xd0, 0x7f,
+  0x36, 0x61, 0xab, 0x24, 0x7e, 0x9f, 0xf8, 0x2d, 0x3f, 0x92, 0x29, 0x63,
+  0xbe, 0xcb, 0x10, 0xdb, 0x0d, 0x40, 0x36, 0x02, 0xa0, 0xd4, 0x17, 0xa2,
+  0x8d, 0x7f, 0x7e, 0x7c, 0x99, 0xaf, 0x45, 0x5a, 0x40, 0xcd, 0xa2, 0x6b,
+  0x5c, 0xbe, 0x0e, 0xf3, 0xd3, 0x87, 0xfc, 0xa1, 0x10, 0xca, 0xaa, 0x33,
+  0xb7, 0xba, 0x4b, 0xc0, 0x3d, 0xa4, 0x21, 0x8c, 0x17, 0x9c, 0xcf, 0xd8,
+  0xbf, 0xe6, 0x57, 0xfe, 0xcd, 0xeb, 0xfa, 0x30, 0x1a, 0xd5, 0xfe, 0xe8,
+  0x25, 0x97, 0xa9, 0xbe, 0x3b, 0xea,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4d:5f:2c:34:08:b2:4c:20:cd:6d:50:7e:24:4d:c9:ec
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+        Validity
+            Not Before: Feb  8 00:00:00 2010 GMT
+            Not After : Feb  7 23:59:59 2020 GMT
+        Subject: C=US, O=Thawte, Inc., CN=Thawte SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:99:e4:85:5b:76:49:7d:2f:05:d8:c5:ac:c8:c8:
+                    a9:d3:dc:98:e6:d7:34:a6:2f:0c:f2:22:26:d8:a3:
+                    c9:14:4c:8f:05:a4:45:e8:14:0c:58:90:05:1a:b7:
+                    c5:c1:06:a5:80:af:bb:1d:49:6b:52:34:88:c3:59:
+                    e7:ef:6b:c4:27:41:8c:2b:66:1d:d0:e0:a3:97:98:
+                    19:34:4b:41:d5:98:d5:c7:05:ad:a2:e4:d7:ed:0c:
+                    ad:4f:c1:b5:b0:21:fd:3e:50:53:b2:c4:90:d0:d4:
+                    30:67:6c:9a:f1:0e:74:c4:c2:dc:8a:e8:97:ff:c9:
+                    92:ae:01:8a:56:0a:98:32:b0:00:23:ec:90:1a:60:
+                    c3:ed:bb:3a:cb:0f:63:9f:0d:44:c9:52:e1:25:96:
+                    bf:ed:50:95:89:7f:56:14:b1:b7:61:1d:1c:07:8c:
+                    3a:2c:f7:ff:80:de:39:45:d5:af:1a:d1:78:d8:c7:
+                    71:6a:a3:19:a7:32:50:21:e9:f2:0e:a1:c6:13:03:
+                    44:48:d1:66:a8:52:57:d7:11:b4:93:8b:e5:99:9f:
+                    5d:e7:78:51:e5:4d:f6:b7:59:b4:76:b5:09:37:4d:
+                    06:38:13:7a:1c:08:98:5c:c4:48:4a:cb:52:a0:a9:
+                    f8:b1:9d:8e:7b:79:b0:20:2f:3c:96:a8:11:62:47:
+                    bb:11
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.thawte.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.thawte.com/ThawtePCA.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-9
+            X509v3 Subject Key Identifier: 
+                A7:A2:83:BB:34:45:40:3D:FC:D5:30:4F:12:B9:3E:A1:01:9F:F6:DB
+            X509v3 Authority Key Identifier: 
+                keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+    Signature Algorithm: sha1WithRSAEncryption
+         80:22:80:e0:6c:c8:95:16:d7:57:26:87:f3:72:34:db:c6:72:
+         56:27:3e:d3:96:f6:2e:25:91:a5:3e:33:97:a7:4b:e5:2f:fb:
+         25:7d:2f:07:61:fa:6f:83:74:4c:4c:53:72:20:a4:7a:cf:51:
+         51:56:81:88:b0:6d:1f:36:2c:c8:2b:b1:88:99:c1:fe:44:ab:
+         48:51:7c:d8:f2:44:64:2a:d8:71:a7:fb:1a:2f:f9:19:8d:34:
+         b2:23:bf:c4:4c:55:1d:8e:44:e8:aa:5d:9a:dd:9f:fd:03:c7:
+         ba:24:43:8d:2d:47:44:db:f6:d8:98:c8:b2:f9:da:ef:ed:29:
+         5c:69:12:fa:d1:23:96:0f:bf:9c:0d:f2:79:45:53:37:9a:56:
+         2f:e8:57:10:70:f6:ee:89:0c:49:89:9a:c1:23:f5:c2:2a:cc:
+         41:cf:22:ab:65:6e:b7:94:82:6d:2f:40:5f:58:de:eb:95:2b:
+         a6:72:68:52:19:91:2a:ae:75:9d:4e:92:e6:ca:de:54:ea:18:
+         ab:25:3c:e6:64:a6:79:1f:26:7d:61:ed:7d:d2:e5:71:55:d8:
+         93:17:7c:14:38:30:3c:df:86:e3:4c:ad:49:e3:97:59:ce:1b:
+         9b:2b:ce:dc:65:d4:0b:28:6b:4e:84:46:51:44:f7:33:08:2d:
+         58:97:21:ae
+-----BEGIN CERTIFICATE-----
+MIIEbDCCA1SgAwIBAgIQTV8sNAiyTCDNbVB+JE3J7DANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjA4MDAwMDAwWhcNMjAw
+MjA3MjM1OTU5WjA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMu
+MRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAmeSFW3ZJfS8F2MWsyMip09yY5tc0pi8M8iIm2KPJFEyPBaRF6BQM
+WJAFGrfFwQalgK+7HUlrUjSIw1nn72vEJ0GMK2Yd0OCjl5gZNEtB1ZjVxwWtouTX
+7QytT8G1sCH9PlBTssSQ0NQwZ2ya8Q50xMLciuiX/8mSrgGKVgqYMrAAI+yQGmDD
+7bs6yw9jnw1EyVLhJZa/7VCViX9WFLG3YR0cB4w6LPf/gN45RdWvGtF42MdxaqMZ
+pzJQIenyDqHGEwNESNFmqFJX1xG0k4vlmZ9d53hR5U32t1m0drUJN00GOBN6HAiY
+XMRISstSoKn4sZ2Oe3mwIC88lqgRYke7EQIDAQABo4H7MIH4MDIGCCsGAQUFBwEB
+BCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29jc3AudGhhd3RlLmNvbTASBgNVHRMB
+Af8ECDAGAQH/AgEAMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVQQ0EuY3JsMA4GA1UdDwEB/wQEAwIBBjAoBgNVHREEITAfpB0w
+GzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItOTAdBgNVHQ4EFgQUp6KDuzRFQD38
+1TBPErk+oQGf9tswHwYDVR0jBBgwFoAUe1tFz6/Oy3r9MZIaarbzRutXSFAwDQYJ
+KoZIhvcNAQEFBQADggEBAIAigOBsyJUW11cmh/NyNNvGclYnPtOW9i4lkaU+M5en
+S+Uv+yV9Lwdh+m+DdExMU3IgpHrPUVFWgYiwbR82LMgrsYiZwf5Eq0hRfNjyRGQq
+2HGn+xov+RmNNLIjv8RMVR2OROiqXZrdn/0Dx7okQ40tR0Tb9tiYyLL52u/tKVxp
+EvrRI5YPv5wN8nlFUzeaVi/oVxBw9u6JDEmJmsEj9cIqzEHPIqtlbreUgm0vQF9Y
+3uuVK6ZyaFIZkSqudZ1OkubK3lTqGKslPOZkpnkfJn1h7X3S5XFV2JMXfBQ4MDzf
+huNMrUnjl1nOG5srztxl1Asoa06ERlFE9zMILViXIa4=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert34[] = {
+  0x30, 0x82, 0x04, 0x6c, 0x30, 0x82, 0x03, 0x54, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x4d, 0x5f, 0x2c, 0x34, 0x08, 0xb2, 0x4c, 0x20, 0xcd,
+  0x6d, 0x50, 0x7e, 0x24, 0x4d, 0xc9, 0xec, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+  0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+  0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+  0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+  0x32, 0x30, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3c,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x54,
+  0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41,
+  0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+  0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0xe4, 0x85,
+  0x5b, 0x76, 0x49, 0x7d, 0x2f, 0x05, 0xd8, 0xc5, 0xac, 0xc8, 0xc8, 0xa9,
+  0xd3, 0xdc, 0x98, 0xe6, 0xd7, 0x34, 0xa6, 0x2f, 0x0c, 0xf2, 0x22, 0x26,
+  0xd8, 0xa3, 0xc9, 0x14, 0x4c, 0x8f, 0x05, 0xa4, 0x45, 0xe8, 0x14, 0x0c,
+  0x58, 0x90, 0x05, 0x1a, 0xb7, 0xc5, 0xc1, 0x06, 0xa5, 0x80, 0xaf, 0xbb,
+  0x1d, 0x49, 0x6b, 0x52, 0x34, 0x88, 0xc3, 0x59, 0xe7, 0xef, 0x6b, 0xc4,
+  0x27, 0x41, 0x8c, 0x2b, 0x66, 0x1d, 0xd0, 0xe0, 0xa3, 0x97, 0x98, 0x19,
+  0x34, 0x4b, 0x41, 0xd5, 0x98, 0xd5, 0xc7, 0x05, 0xad, 0xa2, 0xe4, 0xd7,
+  0xed, 0x0c, 0xad, 0x4f, 0xc1, 0xb5, 0xb0, 0x21, 0xfd, 0x3e, 0x50, 0x53,
+  0xb2, 0xc4, 0x90, 0xd0, 0xd4, 0x30, 0x67, 0x6c, 0x9a, 0xf1, 0x0e, 0x74,
+  0xc4, 0xc2, 0xdc, 0x8a, 0xe8, 0x97, 0xff, 0xc9, 0x92, 0xae, 0x01, 0x8a,
+  0x56, 0x0a, 0x98, 0x32, 0xb0, 0x00, 0x23, 0xec, 0x90, 0x1a, 0x60, 0xc3,
+  0xed, 0xbb, 0x3a, 0xcb, 0x0f, 0x63, 0x9f, 0x0d, 0x44, 0xc9, 0x52, 0xe1,
+  0x25, 0x96, 0xbf, 0xed, 0x50, 0x95, 0x89, 0x7f, 0x56, 0x14, 0xb1, 0xb7,
+  0x61, 0x1d, 0x1c, 0x07, 0x8c, 0x3a, 0x2c, 0xf7, 0xff, 0x80, 0xde, 0x39,
+  0x45, 0xd5, 0xaf, 0x1a, 0xd1, 0x78, 0xd8, 0xc7, 0x71, 0x6a, 0xa3, 0x19,
+  0xa7, 0x32, 0x50, 0x21, 0xe9, 0xf2, 0x0e, 0xa1, 0xc6, 0x13, 0x03, 0x44,
+  0x48, 0xd1, 0x66, 0xa8, 0x52, 0x57, 0xd7, 0x11, 0xb4, 0x93, 0x8b, 0xe5,
+  0x99, 0x9f, 0x5d, 0xe7, 0x78, 0x51, 0xe5, 0x4d, 0xf6, 0xb7, 0x59, 0xb4,
+  0x76, 0xb5, 0x09, 0x37, 0x4d, 0x06, 0x38, 0x13, 0x7a, 0x1c, 0x08, 0x98,
+  0x5c, 0xc4, 0x48, 0x4a, 0xcb, 0x52, 0xa0, 0xa9, 0xf8, 0xb1, 0x9d, 0x8e,
+  0x7b, 0x79, 0xb0, 0x20, 0x2f, 0x3c, 0x96, 0xa8, 0x11, 0x62, 0x47, 0xbb,
+  0x11, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfb, 0x30, 0x81, 0xf8,
+  0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+  0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+  0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30,
+  0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50,
+  0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x28,
+  0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30,
+  0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49,
+  0x2d, 0x32, 0x2d, 0x39, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0xa7, 0xa2, 0x83, 0xbb, 0x34, 0x45, 0x40, 0x3d, 0xfc,
+  0xd5, 0x30, 0x4f, 0x12, 0xb9, 0x3e, 0xa1, 0x01, 0x9f, 0xf6, 0xdb, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a,
+  0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x80, 0x22, 0x80, 0xe0, 0x6c, 0xc8, 0x95, 0x16,
+  0xd7, 0x57, 0x26, 0x87, 0xf3, 0x72, 0x34, 0xdb, 0xc6, 0x72, 0x56, 0x27,
+  0x3e, 0xd3, 0x96, 0xf6, 0x2e, 0x25, 0x91, 0xa5, 0x3e, 0x33, 0x97, 0xa7,
+  0x4b, 0xe5, 0x2f, 0xfb, 0x25, 0x7d, 0x2f, 0x07, 0x61, 0xfa, 0x6f, 0x83,
+  0x74, 0x4c, 0x4c, 0x53, 0x72, 0x20, 0xa4, 0x7a, 0xcf, 0x51, 0x51, 0x56,
+  0x81, 0x88, 0xb0, 0x6d, 0x1f, 0x36, 0x2c, 0xc8, 0x2b, 0xb1, 0x88, 0x99,
+  0xc1, 0xfe, 0x44, 0xab, 0x48, 0x51, 0x7c, 0xd8, 0xf2, 0x44, 0x64, 0x2a,
+  0xd8, 0x71, 0xa7, 0xfb, 0x1a, 0x2f, 0xf9, 0x19, 0x8d, 0x34, 0xb2, 0x23,
+  0xbf, 0xc4, 0x4c, 0x55, 0x1d, 0x8e, 0x44, 0xe8, 0xaa, 0x5d, 0x9a, 0xdd,
+  0x9f, 0xfd, 0x03, 0xc7, 0xba, 0x24, 0x43, 0x8d, 0x2d, 0x47, 0x44, 0xdb,
+  0xf6, 0xd8, 0x98, 0xc8, 0xb2, 0xf9, 0xda, 0xef, 0xed, 0x29, 0x5c, 0x69,
+  0x12, 0xfa, 0xd1, 0x23, 0x96, 0x0f, 0xbf, 0x9c, 0x0d, 0xf2, 0x79, 0x45,
+  0x53, 0x37, 0x9a, 0x56, 0x2f, 0xe8, 0x57, 0x10, 0x70, 0xf6, 0xee, 0x89,
+  0x0c, 0x49, 0x89, 0x9a, 0xc1, 0x23, 0xf5, 0xc2, 0x2a, 0xcc, 0x41, 0xcf,
+  0x22, 0xab, 0x65, 0x6e, 0xb7, 0x94, 0x82, 0x6d, 0x2f, 0x40, 0x5f, 0x58,
+  0xde, 0xeb, 0x95, 0x2b, 0xa6, 0x72, 0x68, 0x52, 0x19, 0x91, 0x2a, 0xae,
+  0x75, 0x9d, 0x4e, 0x92, 0xe6, 0xca, 0xde, 0x54, 0xea, 0x18, 0xab, 0x25,
+  0x3c, 0xe6, 0x64, 0xa6, 0x79, 0x1f, 0x26, 0x7d, 0x61, 0xed, 0x7d, 0xd2,
+  0xe5, 0x71, 0x55, 0xd8, 0x93, 0x17, 0x7c, 0x14, 0x38, 0x30, 0x3c, 0xdf,
+  0x86, 0xe3, 0x4c, 0xad, 0x49, 0xe3, 0x97, 0x59, 0xce, 0x1b, 0x9b, 0x2b,
+  0xce, 0xdc, 0x65, 0xd4, 0x0b, 0x28, 0x6b, 0x4e, 0x84, 0x46, 0x51, 0x44,
+  0xf7, 0x33, 0x08, 0x2d, 0x58, 0x97, 0x21, 0xae,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:47:10
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Dec 15 08:00:00 2006 GMT
+            Not After : Jan 28 12:00:00 2028 GMT
+        Subject: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a6:cf:24:0e:be:2e:6f:28:99:45:42:c4:ab:3e:
+                    21:54:9b:0b:d3:7f:84:70:fa:12:b3:cb:bf:87:5f:
+                    c6:7f:86:d3:b2:30:5c:d6:fd:ad:f1:7b:dc:e5:f8:
+                    60:96:09:92:10:f5:d0:53:de:fb:7b:7e:73:88:ac:
+                    52:88:7b:4a:a6:ca:49:a6:5e:a8:a7:8c:5a:11:bc:
+                    7a:82:eb:be:8c:e9:b3:ac:96:25:07:97:4a:99:2a:
+                    07:2f:b4:1e:77:bf:8a:0f:b5:02:7c:1b:96:b8:c5:
+                    b9:3a:2c:bc:d6:12:b9:eb:59:7d:e2:d0:06:86:5f:
+                    5e:49:6a:b5:39:5e:88:34:ec:bc:78:0c:08:98:84:
+                    6c:a8:cd:4b:b4:a0:7d:0c:79:4d:f0:b8:2d:cb:21:
+                    ca:d5:6c:5b:7d:e1:a0:29:84:a1:f9:d3:94:49:cb:
+                    24:62:91:20:bc:dd:0b:d5:d9:cc:f9:ea:27:0a:2b:
+                    73:91:c6:9d:1b:ac:c8:cb:e8:e0:a0:f4:2f:90:8b:
+                    4d:fb:b0:36:1b:f6:19:7a:85:e0:6d:f2:61:13:88:
+                    5c:9f:e0:93:0a:51:97:8a:5a:ce:af:ab:d5:f7:aa:
+                    09:aa:60:bd:dc:d9:5f:df:72:a9:60:13:5e:00:01:
+                    c9:4a:fa:3f:a4:ea:07:03:21:02:8e:82:ca:03:c2:
+                    9b:8f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Subject Key Identifier: 
+                9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3a:0f:cd:26:4d:38:30:08:a8:c6:fc:5c:d8:08:7a:ef:fa:1c:
+         2a:03:ce:32:ae:44:96:e1:52:03:95:0a:52:d6:67:af:5b:96:
+         7c:dd:19:8b:30:5b:36:3a:6b:6e:a0:15:c6:82:a1:cb:39:66:
+         00:57:8b:02:a2:6e:85:fb:ac:55:5a:b8:15:50:1a:90:de:09:
+         48:ec:a8:f6:57:1c:18:31:bd:c6:7d:c8:bd:eb:c2:a7:39:51:
+         6d:a2:ff:1c:78:de:1c:27:04:e1:cf:24:95:e8:0e:e4:d5:1f:
+         b0:f9:fb:50:ca:cb:6e:9e:62:26:78:86:f5:c4:f5:78:8f:dd:
+         72:af:6e:2e:d5:9e:dd:ce:3c:cb:b8:c7:2d:54:60:d7:e5:9c:
+         02:4b:86:44:f0:57:51:2b:cd:0a:9b:3c:b1:f5:3a:4c:1d:8a:
+         c5:f0:30:3e:65:87:c4:0e:5f:6e:4a:ac:8a:a8:1e:e7:fa:e4:
+         33:80:15:84:56:65:25:9b:fb:9e:30:88:cb:91:16:c1:05:c3:
+         a9:24:ec:21:d2:d5:b0:fc:b7:23:46:a7:9d:f7:f7:c6:53:12:
+         78:37:b4:13:73:8f:37:97:5e:04:9b:f9:99:8b:93:3e:26:42:
+         97:9f:fd:1e:b5:d5:cb:88:48:34:a2:66:a0:fa:ac:72:8f:dd:
+         47:2f:82:74
+-----BEGIN CERTIFICATE-----
+MIIEdzCCA1+gAwIBAgILBAAAAAABL07hRxAwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNjEyMTUwODAw
+MDBaFw0yODAxMjgxMjAwMDBaMEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBD
+QSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps8kDr4ubyiZRULEqz4h
+VJsL03+EcPoSs8u/h1/Gf4bTsjBc1v2t8Xvc5fhglgmSEPXQU977e35ziKxSiHtK
+pspJpl6op4xaEbx6guu+jOmzrJYlB5dKmSoHL7Qed7+KD7UCfBuWuMW5Oiy81hK5
+61l94tAGhl9eSWq1OV6INOy8eAwImIRsqM1LtKB9DHlN8LgtyyHK1WxbfeGgKYSh
++dOUScskYpEgvN0L1dnM+eonCitzkcadG6zIy+jgoPQvkItN+7A2G/YZeoXgbfJh
+E4hcn+CTClGXilrOr6vV96oJqmC93Nlf33KpYBNeAAHJSvo/pOoHAyECjoLKA8Kb
+jwIDAQABo4IBTTCCAUkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+HQYDVR0OBBYEFJviB1dnHB7AagbeWbSaLd/cGYYuMEcGA1UdIARAMD4wPAYEVR0g
+ADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBv
+c2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmdsb2JhbHNpZ24u
+bmV0L3Jvb3QuY3JsMD0GCCsGAQUFBwEBBDEwLzAtBggrBgEFBQcwAYYhaHR0cDov
+L29jc3AuZ2xvYmFsc2lnbi5jb20vcm9vdHIxMCkGA1UdJQQiMCAGCCsGAQUFBwMB
+BggrBgEFBQcDAgYKKwYBBAGCNwoDAzAfBgNVHSMEGDAWgBRge2YaRQ2XyolQL30E
+zTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEAOg/NJk04MAioxvxc2Ah67/ocKgPO
+Mq5EluFSA5UKUtZnr1uWfN0ZizBbNjprbqAVxoKhyzlmAFeLAqJuhfusVVq4FVAa
+kN4JSOyo9lccGDG9xn3IvevCpzlRbaL/HHjeHCcE4c8klegO5NUfsPn7UMrLbp5i
+JniG9cT1eI/dcq9uLtWe3c48y7jHLVRg1+WcAkuGRPBXUSvNCps8sfU6TB2KxfAw
+PmWHxA5fbkqsiqge5/rkM4AVhFZlJZv7njCIy5EWwQXDqSTsIdLVsPy3I0annff3
+xlMSeDe0E3OPN5deBJv5mYuTPiZCl5/9HrXVy4hINKJmoPqsco/dRy+CdA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert35[] = {
+  0x30, 0x82, 0x04, 0x77, 0x30, 0x82, 0x03, 0x5f, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x47, 0x10, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x31, 0x35, 0x30, 0x38, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x30, 0x31, 0x32, 0x38, 0x31,
+  0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43,
+  0x41, 0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
+  0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+  0x69, 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e,
+  0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+  0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa6, 0xcf, 0x24,
+  0x0e, 0xbe, 0x2e, 0x6f, 0x28, 0x99, 0x45, 0x42, 0xc4, 0xab, 0x3e, 0x21,
+  0x54, 0x9b, 0x0b, 0xd3, 0x7f, 0x84, 0x70, 0xfa, 0x12, 0xb3, 0xcb, 0xbf,
+  0x87, 0x5f, 0xc6, 0x7f, 0x86, 0xd3, 0xb2, 0x30, 0x5c, 0xd6, 0xfd, 0xad,
+  0xf1, 0x7b, 0xdc, 0xe5, 0xf8, 0x60, 0x96, 0x09, 0x92, 0x10, 0xf5, 0xd0,
+  0x53, 0xde, 0xfb, 0x7b, 0x7e, 0x73, 0x88, 0xac, 0x52, 0x88, 0x7b, 0x4a,
+  0xa6, 0xca, 0x49, 0xa6, 0x5e, 0xa8, 0xa7, 0x8c, 0x5a, 0x11, 0xbc, 0x7a,
+  0x82, 0xeb, 0xbe, 0x8c, 0xe9, 0xb3, 0xac, 0x96, 0x25, 0x07, 0x97, 0x4a,
+  0x99, 0x2a, 0x07, 0x2f, 0xb4, 0x1e, 0x77, 0xbf, 0x8a, 0x0f, 0xb5, 0x02,
+  0x7c, 0x1b, 0x96, 0xb8, 0xc5, 0xb9, 0x3a, 0x2c, 0xbc, 0xd6, 0x12, 0xb9,
+  0xeb, 0x59, 0x7d, 0xe2, 0xd0, 0x06, 0x86, 0x5f, 0x5e, 0x49, 0x6a, 0xb5,
+  0x39, 0x5e, 0x88, 0x34, 0xec, 0xbc, 0x78, 0x0c, 0x08, 0x98, 0x84, 0x6c,
+  0xa8, 0xcd, 0x4b, 0xb4, 0xa0, 0x7d, 0x0c, 0x79, 0x4d, 0xf0, 0xb8, 0x2d,
+  0xcb, 0x21, 0xca, 0xd5, 0x6c, 0x5b, 0x7d, 0xe1, 0xa0, 0x29, 0x84, 0xa1,
+  0xf9, 0xd3, 0x94, 0x49, 0xcb, 0x24, 0x62, 0x91, 0x20, 0xbc, 0xdd, 0x0b,
+  0xd5, 0xd9, 0xcc, 0xf9, 0xea, 0x27, 0x0a, 0x2b, 0x73, 0x91, 0xc6, 0x9d,
+  0x1b, 0xac, 0xc8, 0xcb, 0xe8, 0xe0, 0xa0, 0xf4, 0x2f, 0x90, 0x8b, 0x4d,
+  0xfb, 0xb0, 0x36, 0x1b, 0xf6, 0x19, 0x7a, 0x85, 0xe0, 0x6d, 0xf2, 0x61,
+  0x13, 0x88, 0x5c, 0x9f, 0xe0, 0x93, 0x0a, 0x51, 0x97, 0x8a, 0x5a, 0xce,
+  0xaf, 0xab, 0xd5, 0xf7, 0xaa, 0x09, 0xaa, 0x60, 0xbd, 0xdc, 0xd9, 0x5f,
+  0xdf, 0x72, 0xa9, 0x60, 0x13, 0x5e, 0x00, 0x01, 0xc9, 0x4a, 0xfa, 0x3f,
+  0xa4, 0xea, 0x07, 0x03, 0x21, 0x02, 0x8e, 0x82, 0xca, 0x03, 0xc2, 0x9b,
+  0x8f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x4d, 0x30, 0x82,
+  0x01, 0x49, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d,
+  0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x9b, 0xe2,
+  0x07, 0x57, 0x67, 0x1c, 0x1e, 0xc0, 0x6a, 0x06, 0xde, 0x59, 0xb4, 0x9a,
+  0x2d, 0xdf, 0xdc, 0x19, 0x86, 0x2e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20,
+  0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+  0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+  0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24,
+  0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+  0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f,
+  0x74, 0x72, 0x31, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x22,
+  0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x0a,
+  0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60,
+  0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04,
+  0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x3a, 0x0f, 0xcd, 0x26, 0x4d, 0x38, 0x30, 0x08, 0xa8,
+  0xc6, 0xfc, 0x5c, 0xd8, 0x08, 0x7a, 0xef, 0xfa, 0x1c, 0x2a, 0x03, 0xce,
+  0x32, 0xae, 0x44, 0x96, 0xe1, 0x52, 0x03, 0x95, 0x0a, 0x52, 0xd6, 0x67,
+  0xaf, 0x5b, 0x96, 0x7c, 0xdd, 0x19, 0x8b, 0x30, 0x5b, 0x36, 0x3a, 0x6b,
+  0x6e, 0xa0, 0x15, 0xc6, 0x82, 0xa1, 0xcb, 0x39, 0x66, 0x00, 0x57, 0x8b,
+  0x02, 0xa2, 0x6e, 0x85, 0xfb, 0xac, 0x55, 0x5a, 0xb8, 0x15, 0x50, 0x1a,
+  0x90, 0xde, 0x09, 0x48, 0xec, 0xa8, 0xf6, 0x57, 0x1c, 0x18, 0x31, 0xbd,
+  0xc6, 0x7d, 0xc8, 0xbd, 0xeb, 0xc2, 0xa7, 0x39, 0x51, 0x6d, 0xa2, 0xff,
+  0x1c, 0x78, 0xde, 0x1c, 0x27, 0x04, 0xe1, 0xcf, 0x24, 0x95, 0xe8, 0x0e,
+  0xe4, 0xd5, 0x1f, 0xb0, 0xf9, 0xfb, 0x50, 0xca, 0xcb, 0x6e, 0x9e, 0x62,
+  0x26, 0x78, 0x86, 0xf5, 0xc4, 0xf5, 0x78, 0x8f, 0xdd, 0x72, 0xaf, 0x6e,
+  0x2e, 0xd5, 0x9e, 0xdd, 0xce, 0x3c, 0xcb, 0xb8, 0xc7, 0x2d, 0x54, 0x60,
+  0xd7, 0xe5, 0x9c, 0x02, 0x4b, 0x86, 0x44, 0xf0, 0x57, 0x51, 0x2b, 0xcd,
+  0x0a, 0x9b, 0x3c, 0xb1, 0xf5, 0x3a, 0x4c, 0x1d, 0x8a, 0xc5, 0xf0, 0x30,
+  0x3e, 0x65, 0x87, 0xc4, 0x0e, 0x5f, 0x6e, 0x4a, 0xac, 0x8a, 0xa8, 0x1e,
+  0xe7, 0xfa, 0xe4, 0x33, 0x80, 0x15, 0x84, 0x56, 0x65, 0x25, 0x9b, 0xfb,
+  0x9e, 0x30, 0x88, 0xcb, 0x91, 0x16, 0xc1, 0x05, 0xc3, 0xa9, 0x24, 0xec,
+  0x21, 0xd2, 0xd5, 0xb0, 0xfc, 0xb7, 0x23, 0x46, 0xa7, 0x9d, 0xf7, 0xf7,
+  0xc6, 0x53, 0x12, 0x78, 0x37, 0xb4, 0x13, 0x73, 0x8f, 0x37, 0x97, 0x5e,
+  0x04, 0x9b, 0xf9, 0x99, 0x8b, 0x93, 0x3e, 0x26, 0x42, 0x97, 0x9f, 0xfd,
+  0x1e, 0xb5, 0xd5, 0xcb, 0x88, 0x48, 0x34, 0xa2, 0x66, 0xa0, 0xfa, 0xac,
+  0x72, 0x8f, 0xdd, 0x47, 0x2f, 0x82, 0x74,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:3f:11
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Domain Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:a3:cd:c0:df:33:40:26:eb:de:5a:d7:94:66:
+                    d4:01:63:cc:33:44:89:e0:e2:b8:c2:47:0d:8f:ad:
+                    69:86:1c:a8:73:42:0b:f1:72:fb:2d:ac:b5:11:72:
+                    83:22:f6:56:e7:2e:c5:67:71:9d:00:1c:32:bc:e3:
+                    ed:2e:08:45:a9:e6:fa:dd:c8:8c:83:05:c1:6f:4b:
+                    d0:26:4a:0b:f6:1b:45:c0:4d:7e:93:bc:0d:27:84:
+                    ed:30:a3:e9:c6:26:26:dd:2d:1f:d8:8b:c3:ce:19:
+                    d0:5b:fc:08:9f:e4:d8:e2:35:e4:a0:68:a6:f6:0d:
+                    a3:74:60:42:b2:97:82:24:8e:41:a4:f2:2e:5e:b6:
+                    8e:a7:6e:d9:6c:7f:0d:3b:24:35:6a:d0:ab:5b:6a:
+                    f7:97:02:00:3f:51:a6:a7:6e:73:ca:77:0d:76:7c:
+                    9b:b6:30:1a:1a:9c:f7:1f:28:7b:0e:8b:47:1f:e7:
+                    7f:05:8c:c6:c9:c8:bb:cf:e9:dc:7a:41:2e:a1:86:
+                    da:d4:39:b2:e2:13:40:a6:a8:3a:fa:0f:53:1e:4f:
+                    ec:6e:98:09:1b:ca:9a:77:b3:55:85:85:e9:2e:16:
+                    b5:9d:5e:54:f1:4a:7a:6c:39:ba:6e:17:06:34:b3:
+                    b2:42:e1:f7:f3:9c:9a:0b:11:44:de:6a:78:8e:b1:
+                    13:4f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                96:AD:FA:B0:5B:B9:83:64:2A:76:C2:1C:8A:69:DA:42:DC:FE:FD:28
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         7e:9a:13:39:71:69:a0:fc:8c:35:ac:af:b4:d6:de:64:ea:33:
+         6f:95:53:92:71:ad:4c:c0:fb:d0:6b:ba:80:0e:c2:0a:e6:37:
+         fa:d2:25:a3:22:f7:89:9f:52:12:43:2f:bb:c4:fc:6c:ce:e4:
+         aa:9d:f6:9d:57:7b:cc:2a:ac:75:49:1b:54:66:cf:a7:e9:b9:
+         b0:c2:7c:70:23:fb:9c:97:00:f2:25:a4:d9:a1:0a:5d:85:06:
+         1d:1a:87:f5:2d:54:c5:64:21:8e:ac:aa:ec:19:3e:9b:ff:c0:
+         67:a7:2e:00:e3:f1:81:40:00:5b:83:e2:a8:a7:ef:35:50:83:
+         c0:f4:9b:88:2a:89:a9:a9:9c:2f:82:b9:18:9e:fa:eb:47:24:
+         6e:13:ee:b2:8c:f0:42:37:5e:e6:8f:91:bc:a5:5f:51:2b:ae:
+         bb:8c:76:31:4e:53:11:79:ec:11:4e:38:73:e5:1a:66:70:f4:
+         82:f7:7b:10:55:f8:bb:a5:c3:1d:e5:d3:f6:bc:fa:28:b6:31:
+         10:d5:fe:91:23:a4:21:3f:ba:4c:91:8f:87:c7:82:ab:38:c2:
+         01:73:89:48:1a:f9:0c:91:b9:95:fb:6d:21:5f:03:c8:bf:7b:
+         74:ef:7b:71:79:b5:3e:73:23:d1:5a:dc:a6:0c:e1:2d:64:65:
+         91:be:c2:b9
+-----BEGIN CERTIFICATE-----
+MIIEhTCCA22gAwIBAgILBAAAAAABL07hPxEwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0
+aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxo83A
+3zNAJuveWteUZtQBY8wzRIng4rjCRw2PrWmGHKhzQgvxcvstrLURcoMi9lbnLsVn
+cZ0AHDK84+0uCEWp5vrdyIyDBcFvS9AmSgv2G0XATX6TvA0nhO0wo+nGJibdLR/Y
+i8POGdBb/Aif5NjiNeSgaKb2DaN0YEKyl4IkjkGk8i5eto6nbtlsfw07JDVq0Ktb
+aveXAgA/UaanbnPKdw12fJu2MBoanPcfKHsOi0cf538FjMbJyLvP6dx6QS6hhtrU
+ObLiE0CmqDr6D1MeT+xumAkbypp3s1WFhekuFrWdXlTxSnpsObpuFwY0s7JC4ffz
+nJoLEUTeaniOsRNPAgMBAAGjggFQMIIBTDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUlq36sFu5g2QqdsIcimnaQtz+/SgwRwYD
+VR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jh
+bHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9j
+cmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEwKQYDVR0l
+BCIwIAYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDMB8GA1UdIwQYMBaA
+FGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQB+mhM5cWmg
+/Iw1rK+01t5k6jNvlVOSca1MwPvQa7qADsIK5jf60iWjIveJn1ISQy+7xPxszuSq
+nfadV3vMKqx1SRtUZs+n6bmwwnxwI/uclwDyJaTZoQpdhQYdGof1LVTFZCGOrKrs
+GT6b/8Bnpy4A4/GBQABbg+Kop+81UIPA9JuIKompqZwvgrkYnvrrRyRuE+6yjPBC
+N17mj5G8pV9RK667jHYxTlMReewRTjhz5RpmcPSC93sQVfi7pcMd5dP2vPootjEQ
+1f6RI6QhP7pMkY+Hx4KrOMIBc4lIGvkMkbmV+20hXwPIv3t073txebU+cyPRWtym
+DOEtZGWRvsK5
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert36[] = {
+  0x30, 0x82, 0x04, 0x85, 0x30, 0x82, 0x03, 0x6d, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x3f, 0x11, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+  0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+  0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x47,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f,
+  0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xa3, 0xcd, 0xc0,
+  0xdf, 0x33, 0x40, 0x26, 0xeb, 0xde, 0x5a, 0xd7, 0x94, 0x66, 0xd4, 0x01,
+  0x63, 0xcc, 0x33, 0x44, 0x89, 0xe0, 0xe2, 0xb8, 0xc2, 0x47, 0x0d, 0x8f,
+  0xad, 0x69, 0x86, 0x1c, 0xa8, 0x73, 0x42, 0x0b, 0xf1, 0x72, 0xfb, 0x2d,
+  0xac, 0xb5, 0x11, 0x72, 0x83, 0x22, 0xf6, 0x56, 0xe7, 0x2e, 0xc5, 0x67,
+  0x71, 0x9d, 0x00, 0x1c, 0x32, 0xbc, 0xe3, 0xed, 0x2e, 0x08, 0x45, 0xa9,
+  0xe6, 0xfa, 0xdd, 0xc8, 0x8c, 0x83, 0x05, 0xc1, 0x6f, 0x4b, 0xd0, 0x26,
+  0x4a, 0x0b, 0xf6, 0x1b, 0x45, 0xc0, 0x4d, 0x7e, 0x93, 0xbc, 0x0d, 0x27,
+  0x84, 0xed, 0x30, 0xa3, 0xe9, 0xc6, 0x26, 0x26, 0xdd, 0x2d, 0x1f, 0xd8,
+  0x8b, 0xc3, 0xce, 0x19, 0xd0, 0x5b, 0xfc, 0x08, 0x9f, 0xe4, 0xd8, 0xe2,
+  0x35, 0xe4, 0xa0, 0x68, 0xa6, 0xf6, 0x0d, 0xa3, 0x74, 0x60, 0x42, 0xb2,
+  0x97, 0x82, 0x24, 0x8e, 0x41, 0xa4, 0xf2, 0x2e, 0x5e, 0xb6, 0x8e, 0xa7,
+  0x6e, 0xd9, 0x6c, 0x7f, 0x0d, 0x3b, 0x24, 0x35, 0x6a, 0xd0, 0xab, 0x5b,
+  0x6a, 0xf7, 0x97, 0x02, 0x00, 0x3f, 0x51, 0xa6, 0xa7, 0x6e, 0x73, 0xca,
+  0x77, 0x0d, 0x76, 0x7c, 0x9b, 0xb6, 0x30, 0x1a, 0x1a, 0x9c, 0xf7, 0x1f,
+  0x28, 0x7b, 0x0e, 0x8b, 0x47, 0x1f, 0xe7, 0x7f, 0x05, 0x8c, 0xc6, 0xc9,
+  0xc8, 0xbb, 0xcf, 0xe9, 0xdc, 0x7a, 0x41, 0x2e, 0xa1, 0x86, 0xda, 0xd4,
+  0x39, 0xb2, 0xe2, 0x13, 0x40, 0xa6, 0xa8, 0x3a, 0xfa, 0x0f, 0x53, 0x1e,
+  0x4f, 0xec, 0x6e, 0x98, 0x09, 0x1b, 0xca, 0x9a, 0x77, 0xb3, 0x55, 0x85,
+  0x85, 0xe9, 0x2e, 0x16, 0xb5, 0x9d, 0x5e, 0x54, 0xf1, 0x4a, 0x7a, 0x6c,
+  0x39, 0xba, 0x6e, 0x17, 0x06, 0x34, 0xb3, 0xb2, 0x42, 0xe1, 0xf7, 0xf3,
+  0x9c, 0x9a, 0x0b, 0x11, 0x44, 0xde, 0x6a, 0x78, 0x8e, 0xb1, 0x13, 0x4f,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x50, 0x30, 0x82, 0x01,
+  0x4c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x96, 0xad, 0xfa, 0xb0, 0x5b, 0xb9, 0x83, 0x64, 0x2a, 0x76, 0xc2, 0x1c,
+  0x8a, 0x69, 0xda, 0x42, 0xdc, 0xfe, 0xfd, 0x28, 0x30, 0x47, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+  0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x25,
+  0x04, 0x22, 0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+  0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f,
+  0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x01, 0x00, 0x7e, 0x9a, 0x13, 0x39, 0x71, 0x69, 0xa0,
+  0xfc, 0x8c, 0x35, 0xac, 0xaf, 0xb4, 0xd6, 0xde, 0x64, 0xea, 0x33, 0x6f,
+  0x95, 0x53, 0x92, 0x71, 0xad, 0x4c, 0xc0, 0xfb, 0xd0, 0x6b, 0xba, 0x80,
+  0x0e, 0xc2, 0x0a, 0xe6, 0x37, 0xfa, 0xd2, 0x25, 0xa3, 0x22, 0xf7, 0x89,
+  0x9f, 0x52, 0x12, 0x43, 0x2f, 0xbb, 0xc4, 0xfc, 0x6c, 0xce, 0xe4, 0xaa,
+  0x9d, 0xf6, 0x9d, 0x57, 0x7b, 0xcc, 0x2a, 0xac, 0x75, 0x49, 0x1b, 0x54,
+  0x66, 0xcf, 0xa7, 0xe9, 0xb9, 0xb0, 0xc2, 0x7c, 0x70, 0x23, 0xfb, 0x9c,
+  0x97, 0x00, 0xf2, 0x25, 0xa4, 0xd9, 0xa1, 0x0a, 0x5d, 0x85, 0x06, 0x1d,
+  0x1a, 0x87, 0xf5, 0x2d, 0x54, 0xc5, 0x64, 0x21, 0x8e, 0xac, 0xaa, 0xec,
+  0x19, 0x3e, 0x9b, 0xff, 0xc0, 0x67, 0xa7, 0x2e, 0x00, 0xe3, 0xf1, 0x81,
+  0x40, 0x00, 0x5b, 0x83, 0xe2, 0xa8, 0xa7, 0xef, 0x35, 0x50, 0x83, 0xc0,
+  0xf4, 0x9b, 0x88, 0x2a, 0x89, 0xa9, 0xa9, 0x9c, 0x2f, 0x82, 0xb9, 0x18,
+  0x9e, 0xfa, 0xeb, 0x47, 0x24, 0x6e, 0x13, 0xee, 0xb2, 0x8c, 0xf0, 0x42,
+  0x37, 0x5e, 0xe6, 0x8f, 0x91, 0xbc, 0xa5, 0x5f, 0x51, 0x2b, 0xae, 0xbb,
+  0x8c, 0x76, 0x31, 0x4e, 0x53, 0x11, 0x79, 0xec, 0x11, 0x4e, 0x38, 0x73,
+  0xe5, 0x1a, 0x66, 0x70, 0xf4, 0x82, 0xf7, 0x7b, 0x10, 0x55, 0xf8, 0xbb,
+  0xa5, 0xc3, 0x1d, 0xe5, 0xd3, 0xf6, 0xbc, 0xfa, 0x28, 0xb6, 0x31, 0x10,
+  0xd5, 0xfe, 0x91, 0x23, 0xa4, 0x21, 0x3f, 0xba, 0x4c, 0x91, 0x8f, 0x87,
+  0xc7, 0x82, 0xab, 0x38, 0xc2, 0x01, 0x73, 0x89, 0x48, 0x1a, 0xf9, 0x0c,
+  0x91, 0xb9, 0x95, 0xfb, 0x6d, 0x21, 0x5f, 0x03, 0xc8, 0xbf, 0x7b, 0x74,
+  0xef, 0x7b, 0x71, 0x79, 0xb5, 0x3e, 0x73, 0x23, 0xd1, 0x5a, 0xdc, 0xa6,
+  0x0c, 0xe1, 0x2d, 0x64, 0x65, 0x91, 0xbe, 0xc2, 0xb9,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:5d:d4
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:a1:46:cc:52:9a:b8:a5:b4:7f:58:d9:cd:d8:
+                    4b:a0:72:0c:97:5b:a6:88:20:c3:b9:3d:46:dc:d0:
+                    5c:52:30:f6:fa:59:4f:85:5f:b0:db:88:b4:a9:5f:
+                    2b:23:48:ac:ab:f5:92:78:14:b6:32:0f:fb:5c:6a:
+                    85:5b:00:90:e0:bb:65:f5:5a:f9:4f:67:7e:c7:6c:
+                    29:ec:93:c0:2b:ca:c4:5e:d8:b0:db:d6:be:3f:9b:
+                    0b:c0:8f:a9:5d:ae:f7:00:02:a4:fc:ba:66:11:38:
+                    77:fe:23:20:25:55:10:c5:bd:82:b9:4c:b1:68:c6:
+                    e2:70:7b:83:5c:13:67:c1:a1:f3:7c:0b:a8:99:9a:
+                    d0:e2:9b:25:31:c8:2b:8d:40:f6:52:63:b1:a0:ad:
+                    5a:2e:f5:79:36:6d:35:2c:0e:dd:05:e4:d0:e2:07:
+                    48:b7:28:5e:2b:d5:58:d5:6c:d0:0c:a1:01:46:01:
+                    5a:8f:c6:af:64:c7:55:01:5d:e1:d1:c6:6c:50:25:
+                    a0:05:ad:00:ab:0c:8d:65:6b:dd:eb:c2:72:54:c9:
+                    0f:3c:00:17:87:22:ef:db:b9:86:78:16:51:ae:77:
+                    d9:a6:28:4d:f3:58:8d:83:67:b9:34:25:9b:1c:51:
+                    80:51:f3:83:92:6a:a3:ae:47:9a:d6:e4:8b:1b:c0:
+                    ed:b1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                B0:B0:4A:FD:1C:75:28:F8:1C:61:AA:13:F6:FA:C1:90:3D:6B:16:A3
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root-r2.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/ExtendedSSLCA
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+
+    Signature Algorithm: sha1WithRSAEncryption
+         2f:49:b6:f2:b6:5a:a4:95:ab:9e:5a:e9:2b:82:9b:cc:90:6b:
+         7c:74:45:20:e7:5e:d8:c7:23:ee:28:35:b1:35:65:2a:a5:51:
+         e0:55:3f:f6:83:67:b4:e4:36:29:b0:da:ec:97:95:a9:8a:05:
+         a3:45:fe:23:2e:52:88:b4:1f:10:80:ad:d2:8b:9f:a3:5f:a8:
+         c5:eb:73:de:79:88:41:98:33:ee:a7:60:18:b1:46:c9:40:10:
+         07:9c:8f:0a:52:c9:13:1a:06:6e:a0:9b:2d:3a:f6:ae:4f:e7:
+         a3:51:35:2a:5b:18:05:12:e5:51:dc:b6:36:62:f3:e1:a4:0f:
+         fb:e4:cf:c3:94:bf:11:ab:a1:59:31:01:f0:cc:53:ec:8f:63:
+         d7:6c:96:d3:48:2a:8a:23:ed:45:56:a8:66:41:ea:01:b9:47:
+         ee:a1:47:0c:14:f1:23:e1:20:73:ca:7d:50:7c:64:38:57:a3:
+         8f:4a:9c:9b:e9:6d:45:cf:44:6b:4d:60:20:40:71:25:b5:46:
+         aa:6c:08:7e:df:c8:fa:c8:56:2a:92:cb:83:b8:79:09:97:2d:
+         5e:a1:01:ce:06:ed:b4:97:c9:04:dc:41:ef:e0:4f:36:4d:e4:
+         40:73:46:ec:11:12:7c:88:5b:34:26:25:4d:ea:dc:18:be:c5:
+         1b:cd:64:c0
+-----BEGIN CERTIFICATE-----
+MIIEhjCCA26gAwIBAgILBAAAAAABL07hXdQwDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTEwNDEzMTAwMDAwWhcNMjIwNDEz
+MTAwMDAwWjBZMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1z
+YTEvMC0GA1UEAxMmR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIC0g
+RzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNoUbMUpq4pbR/WNnN
+2EugcgyXW6aIIMO5PUbc0FxSMPb6WU+FX7DbiLSpXysjSKyr9ZJ4FLYyD/tcaoVb
+AJDgu2X1WvlPZ37HbCnsk8ArysRe2LDb1r4/mwvAj6ldrvcAAqT8umYROHf+IyAl
+VRDFvYK5TLFoxuJwe4NcE2fBofN8C6iZmtDimyUxyCuNQPZSY7GgrVou9Xk2bTUs
+Dt0F5NDiB0i3KF4r1VjVbNAMoQFGAVqPxq9kx1UBXeHRxmxQJaAFrQCrDI1la93r
+wnJUyQ88ABeHIu/buYZ4FlGud9mmKE3zWI2DZ7k0JZscUYBR84OSaqOuR5rW5Isb
+wO2xAgMBAAGjggFaMIIBVjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADAdBgNVHQ4EFgQUsLBK/Rx1KPgcYaoT9vrBkD1rFqMwRwYDVR0gBEAwPjA8
+BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t
+L3JlcG9zaXRvcnkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFs
+c2lnbi5uZXQvcm9vdC1yMi5jcmwwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzAB
+hihodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMCkGA1Ud
+JQQiMCAGCCsGAQUFBwMBBggrBgEFBQcDAgYKKwYBBAGCNwoDAzAfBgNVHSMEGDAW
+gBSb4gdXZxwewGoG3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAL0m28rZa
+pJWrnlrpK4KbzJBrfHRFIOde2Mcj7ig1sTVlKqVR4FU/9oNntOQ2KbDa7JeVqYoF
+o0X+Iy5SiLQfEICt0oufo1+oxetz3nmIQZgz7qdgGLFGyUAQB5yPClLJExoGbqCb
+LTr2rk/no1E1KlsYBRLlUdy2NmLz4aQP++TPw5S/EauhWTEB8MxT7I9j12yW00gq
+iiPtRVaoZkHqAblH7qFHDBTxI+Egc8p9UHxkOFejj0qcm+ltRc9Ea01gIEBxJbVG
+qmwIft/I+shWKpLLg7h5CZctXqEBzgbttJfJBNxB7+BPNk3kQHNG7BESfIhbNCYl
+TercGL7FG81kwA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert37[] = {
+  0x30, 0x82, 0x04, 0x86, 0x30, 0x82, 0x03, 0x6e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x5d, 0xd4, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33,
+  0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x59, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73,
+  0x61, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+  0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69,
+  0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+  0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+  0xa1, 0x46, 0xcc, 0x52, 0x9a, 0xb8, 0xa5, 0xb4, 0x7f, 0x58, 0xd9, 0xcd,
+  0xd8, 0x4b, 0xa0, 0x72, 0x0c, 0x97, 0x5b, 0xa6, 0x88, 0x20, 0xc3, 0xb9,
+  0x3d, 0x46, 0xdc, 0xd0, 0x5c, 0x52, 0x30, 0xf6, 0xfa, 0x59, 0x4f, 0x85,
+  0x5f, 0xb0, 0xdb, 0x88, 0xb4, 0xa9, 0x5f, 0x2b, 0x23, 0x48, 0xac, 0xab,
+  0xf5, 0x92, 0x78, 0x14, 0xb6, 0x32, 0x0f, 0xfb, 0x5c, 0x6a, 0x85, 0x5b,
+  0x00, 0x90, 0xe0, 0xbb, 0x65, 0xf5, 0x5a, 0xf9, 0x4f, 0x67, 0x7e, 0xc7,
+  0x6c, 0x29, 0xec, 0x93, 0xc0, 0x2b, 0xca, 0xc4, 0x5e, 0xd8, 0xb0, 0xdb,
+  0xd6, 0xbe, 0x3f, 0x9b, 0x0b, 0xc0, 0x8f, 0xa9, 0x5d, 0xae, 0xf7, 0x00,
+  0x02, 0xa4, 0xfc, 0xba, 0x66, 0x11, 0x38, 0x77, 0xfe, 0x23, 0x20, 0x25,
+  0x55, 0x10, 0xc5, 0xbd, 0x82, 0xb9, 0x4c, 0xb1, 0x68, 0xc6, 0xe2, 0x70,
+  0x7b, 0x83, 0x5c, 0x13, 0x67, 0xc1, 0xa1, 0xf3, 0x7c, 0x0b, 0xa8, 0x99,
+  0x9a, 0xd0, 0xe2, 0x9b, 0x25, 0x31, 0xc8, 0x2b, 0x8d, 0x40, 0xf6, 0x52,
+  0x63, 0xb1, 0xa0, 0xad, 0x5a, 0x2e, 0xf5, 0x79, 0x36, 0x6d, 0x35, 0x2c,
+  0x0e, 0xdd, 0x05, 0xe4, 0xd0, 0xe2, 0x07, 0x48, 0xb7, 0x28, 0x5e, 0x2b,
+  0xd5, 0x58, 0xd5, 0x6c, 0xd0, 0x0c, 0xa1, 0x01, 0x46, 0x01, 0x5a, 0x8f,
+  0xc6, 0xaf, 0x64, 0xc7, 0x55, 0x01, 0x5d, 0xe1, 0xd1, 0xc6, 0x6c, 0x50,
+  0x25, 0xa0, 0x05, 0xad, 0x00, 0xab, 0x0c, 0x8d, 0x65, 0x6b, 0xdd, 0xeb,
+  0xc2, 0x72, 0x54, 0xc9, 0x0f, 0x3c, 0x00, 0x17, 0x87, 0x22, 0xef, 0xdb,
+  0xb9, 0x86, 0x78, 0x16, 0x51, 0xae, 0x77, 0xd9, 0xa6, 0x28, 0x4d, 0xf3,
+  0x58, 0x8d, 0x83, 0x67, 0xb9, 0x34, 0x25, 0x9b, 0x1c, 0x51, 0x80, 0x51,
+  0xf3, 0x83, 0x92, 0x6a, 0xa3, 0xae, 0x47, 0x9a, 0xd6, 0xe4, 0x8b, 0x1b,
+  0xc0, 0xed, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5a,
+  0x30, 0x82, 0x01, 0x56, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0xb0, 0xb0, 0x4a, 0xfd, 0x1c, 0x75, 0x28, 0xf8, 0x1c,
+  0x61, 0xaa, 0x13, 0xf6, 0xfa, 0xc1, 0x90, 0x3d, 0x6b, 0x16, 0xa3, 0x30,
+  0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74,
+  0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f,
+  0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30,
+  0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f,
+  0x74, 0x2d, 0x72, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x44, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x38, 0x30, 0x36,
+  0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+  0x64, 0x53, 0x53, 0x4c, 0x43, 0x41, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d,
+  0x25, 0x04, 0x22, 0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03,
+  0x02, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03,
+  0x03, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0x9b, 0xe2, 0x07, 0x57, 0x67, 0x1c, 0x1e, 0xc0, 0x6a, 0x06,
+  0xde, 0x59, 0xb4, 0x9a, 0x2d, 0xdf, 0xdc, 0x19, 0x86, 0x2e, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x2f, 0x49, 0xb6, 0xf2, 0xb6, 0x5a,
+  0xa4, 0x95, 0xab, 0x9e, 0x5a, 0xe9, 0x2b, 0x82, 0x9b, 0xcc, 0x90, 0x6b,
+  0x7c, 0x74, 0x45, 0x20, 0xe7, 0x5e, 0xd8, 0xc7, 0x23, 0xee, 0x28, 0x35,
+  0xb1, 0x35, 0x65, 0x2a, 0xa5, 0x51, 0xe0, 0x55, 0x3f, 0xf6, 0x83, 0x67,
+  0xb4, 0xe4, 0x36, 0x29, 0xb0, 0xda, 0xec, 0x97, 0x95, 0xa9, 0x8a, 0x05,
+  0xa3, 0x45, 0xfe, 0x23, 0x2e, 0x52, 0x88, 0xb4, 0x1f, 0x10, 0x80, 0xad,
+  0xd2, 0x8b, 0x9f, 0xa3, 0x5f, 0xa8, 0xc5, 0xeb, 0x73, 0xde, 0x79, 0x88,
+  0x41, 0x98, 0x33, 0xee, 0xa7, 0x60, 0x18, 0xb1, 0x46, 0xc9, 0x40, 0x10,
+  0x07, 0x9c, 0x8f, 0x0a, 0x52, 0xc9, 0x13, 0x1a, 0x06, 0x6e, 0xa0, 0x9b,
+  0x2d, 0x3a, 0xf6, 0xae, 0x4f, 0xe7, 0xa3, 0x51, 0x35, 0x2a, 0x5b, 0x18,
+  0x05, 0x12, 0xe5, 0x51, 0xdc, 0xb6, 0x36, 0x62, 0xf3, 0xe1, 0xa4, 0x0f,
+  0xfb, 0xe4, 0xcf, 0xc3, 0x94, 0xbf, 0x11, 0xab, 0xa1, 0x59, 0x31, 0x01,
+  0xf0, 0xcc, 0x53, 0xec, 0x8f, 0x63, 0xd7, 0x6c, 0x96, 0xd3, 0x48, 0x2a,
+  0x8a, 0x23, 0xed, 0x45, 0x56, 0xa8, 0x66, 0x41, 0xea, 0x01, 0xb9, 0x47,
+  0xee, 0xa1, 0x47, 0x0c, 0x14, 0xf1, 0x23, 0xe1, 0x20, 0x73, 0xca, 0x7d,
+  0x50, 0x7c, 0x64, 0x38, 0x57, 0xa3, 0x8f, 0x4a, 0x9c, 0x9b, 0xe9, 0x6d,
+  0x45, 0xcf, 0x44, 0x6b, 0x4d, 0x60, 0x20, 0x40, 0x71, 0x25, 0xb5, 0x46,
+  0xaa, 0x6c, 0x08, 0x7e, 0xdf, 0xc8, 0xfa, 0xc8, 0x56, 0x2a, 0x92, 0xcb,
+  0x83, 0xb8, 0x79, 0x09, 0x97, 0x2d, 0x5e, 0xa1, 0x01, 0xce, 0x06, 0xed,
+  0xb4, 0x97, 0xc9, 0x04, 0xdc, 0x41, 0xef, 0xe0, 0x4f, 0x36, 0x4d, 0xe4,
+  0x40, 0x73, 0x46, 0xec, 0x11, 0x12, 0x7c, 0x88, 0x5b, 0x34, 0x26, 0x25,
+  0x4d, 0xea, 0xdc, 0x18, 0xbe, 0xc5, 0x1b, 0xcd, 0x64, 0xc0,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            52:42:06:4a:4f:37:fe:43:69:48:7a:96:67:ff:5d:27
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Jun  7 08:09:10 2005 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:f7:c3:38:3f:b4:a8:7f:cf:39:82:51:67:d0:
+                    6d:9f:d2:ff:58:f3:e7:9f:2b:ec:0d:89:54:99:b9:
+                    38:99:16:f7:e0:21:79:48:c2:bb:61:74:12:96:1d:
+                    3c:6a:72:d5:3c:10:67:3a:39:ed:2b:13:cd:66:eb:
+                    95:09:33:a4:6c:97:b1:e8:c6:ec:c1:75:79:9c:46:
+                    5e:8d:ab:d0:6a:fd:b9:2a:55:17:10:54:b3:19:f0:
+                    9a:f6:f1:b1:5d:b6:a7:6d:fb:e0:71:17:6b:a2:88:
+                    fb:00:df:fe:1a:31:77:0c:9a:01:7a:b1:32:e3:2b:
+                    01:07:38:6e:c3:a5:5e:23:bc:45:9b:7b:50:c1:c9:
+                    30:8f:db:e5:2b:7a:d3:5b:fb:33:40:1e:a0:d5:98:
+                    17:bc:8b:87:c3:89:d3:5d:a0:8e:b2:aa:aa:f6:8e:
+                    69:88:06:c5:fa:89:21:f3:08:9d:69:2e:09:33:9b:
+                    29:0d:46:0f:8c:cc:49:34:b0:69:51:bd:f9:06:cd:
+                    68:ad:66:4c:bc:3e:ac:61:bd:0a:88:0e:c8:df:3d:
+                    ee:7c:04:4c:9d:0a:5e:6b:91:d6:ee:c7:ed:28:8d:
+                    ab:4d:87:89:73:d0:6e:a4:d0:1e:16:8b:14:e1:76:
+                    44:03:7f:63:ac:e4:cd:49:9c:c5:92:f4:ab:32:a1:
+                    48:5b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/AddTrustExternalCARoot.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/AddTrustExternalCARoot.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         60:64:39:59:a2:43:65:2e:fd:f9:1f:d6:ae:33:bb:e8:53:13:
+         c4:88:ee:23:1a:6c:ce:d8:64:59:53:53:90:e8:36:df:d4:fc:
+         f3:4e:79:2f:d5:e6:8f:0c:ef:2a:41:6d:71:bd:9b:78:38:23:
+         d3:70:4b:86:0c:fd:12:a7:22:62:12:d8:cc:e0:51:ef:2d:e5:
+         cd:0c:45:a2:ea:da:ed:7e:ec:f7:32:9a:e7:05:35:5e:6e:c2:
+         2c:68:68:9d:ff:8c:f1:ca:55:87:c4:2f:b1:40:06:dc:84:22:
+         5c:6d:b3:cd:d1:9b:1a:0a:33:28:66:16:0c:bd:33:c2:f6:07:
+         f1:e3:a1:79:94:e0:f8:d0:d0:d3:df:52:86:3f:a9:e1:c9:1d:
+         3e:86:84:b1:db:5f:ee:e4:49:43:c1:39:7d:cf:2f:96:a7:75:
+         5d:7e:67:67:84:e5:59:20:40:bf:37:22:bf:07:43:b4:30:e1:
+         43:8a:cd:03:5d:6d:b9:29:d9:84:a7:f5:62:63:84:86:d6:37:
+         be:6f:67:bb:ff:62:57:39:9d:0c:4d:b2:2a:61:3d:1d:9c:ef:
+         9a:77:20:a0:2f:ee:1a:72:9d:b0:9d:bf:78:13:27:07:0a:60:
+         11:93:f5:0f:2e:c9:ef:6b:24:83:fe:9b:90:b4:4b:68:81:d0:
+         c2:fa:e0:3f
+-----BEGIN CERTIFICATE-----
+MIIEhjCCA26gAwIBAgIQUkIGSk83/kNpSHqWZ/9dJzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0
+LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0
+qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt
+KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr
+ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX
+vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM
+vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN
+SZzFkvSrMqFIWwIDAQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D
+veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQWRkVHJ1c3RFeHRl
+cm5hbENBUm9vdC5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BZGRU
+cnVzdEV4dGVybmFsQ0FSb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAYGQ5WaJD
+ZS79+R/WrjO76FMTxIjuIxpszthkWVNTkOg239T88055L9XmjwzvKkFtcb2beDgj
+03BLhgz9EqciYhLYzOBR7y3lzQxFoura7X7s9zKa5wU1Xm7CLGhonf+M8cpVh8Qv
+sUAG3IQiXG2zzdGbGgozKGYWDL0zwvYH8eOheZTg+NDQ099Shj+p4ckdPoaEsdtf
+7uRJQ8E5fc8vlqd1XX5nZ4TlWSBAvzcivwdDtDDhQ4rNA11tuSnZhKf1YmOEhtY3
+vm9nu/9iVzmdDE2yKmE9HZzvmncgoC/uGnKdsJ2/eBMnBwpgEZP1Dy7J72skg/6b
+kLRLaIHQwvrgPw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert38[] = {
+  0x30, 0x82, 0x04, 0x86, 0x30, 0x82, 0x03, 0x6e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x52, 0x42, 0x06, 0x4a, 0x4f, 0x37, 0xfe, 0x43, 0x69,
+  0x48, 0x7a, 0x96, 0x67, 0xff, 0x5d, 0x27, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+  0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+  0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+  0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55,
+  0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+  0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xf7, 0xc3, 0x38, 0x3f, 0xb4,
+  0xa8, 0x7f, 0xcf, 0x39, 0x82, 0x51, 0x67, 0xd0, 0x6d, 0x9f, 0xd2, 0xff,
+  0x58, 0xf3, 0xe7, 0x9f, 0x2b, 0xec, 0x0d, 0x89, 0x54, 0x99, 0xb9, 0x38,
+  0x99, 0x16, 0xf7, 0xe0, 0x21, 0x79, 0x48, 0xc2, 0xbb, 0x61, 0x74, 0x12,
+  0x96, 0x1d, 0x3c, 0x6a, 0x72, 0xd5, 0x3c, 0x10, 0x67, 0x3a, 0x39, 0xed,
+  0x2b, 0x13, 0xcd, 0x66, 0xeb, 0x95, 0x09, 0x33, 0xa4, 0x6c, 0x97, 0xb1,
+  0xe8, 0xc6, 0xec, 0xc1, 0x75, 0x79, 0x9c, 0x46, 0x5e, 0x8d, 0xab, 0xd0,
+  0x6a, 0xfd, 0xb9, 0x2a, 0x55, 0x17, 0x10, 0x54, 0xb3, 0x19, 0xf0, 0x9a,
+  0xf6, 0xf1, 0xb1, 0x5d, 0xb6, 0xa7, 0x6d, 0xfb, 0xe0, 0x71, 0x17, 0x6b,
+  0xa2, 0x88, 0xfb, 0x00, 0xdf, 0xfe, 0x1a, 0x31, 0x77, 0x0c, 0x9a, 0x01,
+  0x7a, 0xb1, 0x32, 0xe3, 0x2b, 0x01, 0x07, 0x38, 0x6e, 0xc3, 0xa5, 0x5e,
+  0x23, 0xbc, 0x45, 0x9b, 0x7b, 0x50, 0xc1, 0xc9, 0x30, 0x8f, 0xdb, 0xe5,
+  0x2b, 0x7a, 0xd3, 0x5b, 0xfb, 0x33, 0x40, 0x1e, 0xa0, 0xd5, 0x98, 0x17,
+  0xbc, 0x8b, 0x87, 0xc3, 0x89, 0xd3, 0x5d, 0xa0, 0x8e, 0xb2, 0xaa, 0xaa,
+  0xf6, 0x8e, 0x69, 0x88, 0x06, 0xc5, 0xfa, 0x89, 0x21, 0xf3, 0x08, 0x9d,
+  0x69, 0x2e, 0x09, 0x33, 0x9b, 0x29, 0x0d, 0x46, 0x0f, 0x8c, 0xcc, 0x49,
+  0x34, 0xb0, 0x69, 0x51, 0xbd, 0xf9, 0x06, 0xcd, 0x68, 0xad, 0x66, 0x4c,
+  0xbc, 0x3e, 0xac, 0x61, 0xbd, 0x0a, 0x88, 0x0e, 0xc8, 0xdf, 0x3d, 0xee,
+  0x7c, 0x04, 0x4c, 0x9d, 0x0a, 0x5e, 0x6b, 0x91, 0xd6, 0xee, 0xc7, 0xed,
+  0x28, 0x8d, 0xab, 0x4d, 0x87, 0x89, 0x73, 0xd0, 0x6e, 0xa4, 0xd0, 0x1e,
+  0x16, 0x8b, 0x14, 0xe1, 0x76, 0x44, 0x03, 0x7f, 0x63, 0xac, 0xe4, 0xcd,
+  0x49, 0x9c, 0xc5, 0x92, 0xf4, 0xab, 0x32, 0xa1, 0x48, 0x5b, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x81, 0xf4, 0x30, 0x81, 0xf1, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+  0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+  0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+  0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+  0x45, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x11,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65,
+  0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
+  0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+  0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x60, 0x64, 0x39, 0x59, 0xa2, 0x43,
+  0x65, 0x2e, 0xfd, 0xf9, 0x1f, 0xd6, 0xae, 0x33, 0xbb, 0xe8, 0x53, 0x13,
+  0xc4, 0x88, 0xee, 0x23, 0x1a, 0x6c, 0xce, 0xd8, 0x64, 0x59, 0x53, 0x53,
+  0x90, 0xe8, 0x36, 0xdf, 0xd4, 0xfc, 0xf3, 0x4e, 0x79, 0x2f, 0xd5, 0xe6,
+  0x8f, 0x0c, 0xef, 0x2a, 0x41, 0x6d, 0x71, 0xbd, 0x9b, 0x78, 0x38, 0x23,
+  0xd3, 0x70, 0x4b, 0x86, 0x0c, 0xfd, 0x12, 0xa7, 0x22, 0x62, 0x12, 0xd8,
+  0xcc, 0xe0, 0x51, 0xef, 0x2d, 0xe5, 0xcd, 0x0c, 0x45, 0xa2, 0xea, 0xda,
+  0xed, 0x7e, 0xec, 0xf7, 0x32, 0x9a, 0xe7, 0x05, 0x35, 0x5e, 0x6e, 0xc2,
+  0x2c, 0x68, 0x68, 0x9d, 0xff, 0x8c, 0xf1, 0xca, 0x55, 0x87, 0xc4, 0x2f,
+  0xb1, 0x40, 0x06, 0xdc, 0x84, 0x22, 0x5c, 0x6d, 0xb3, 0xcd, 0xd1, 0x9b,
+  0x1a, 0x0a, 0x33, 0x28, 0x66, 0x16, 0x0c, 0xbd, 0x33, 0xc2, 0xf6, 0x07,
+  0xf1, 0xe3, 0xa1, 0x79, 0x94, 0xe0, 0xf8, 0xd0, 0xd0, 0xd3, 0xdf, 0x52,
+  0x86, 0x3f, 0xa9, 0xe1, 0xc9, 0x1d, 0x3e, 0x86, 0x84, 0xb1, 0xdb, 0x5f,
+  0xee, 0xe4, 0x49, 0x43, 0xc1, 0x39, 0x7d, 0xcf, 0x2f, 0x96, 0xa7, 0x75,
+  0x5d, 0x7e, 0x67, 0x67, 0x84, 0xe5, 0x59, 0x20, 0x40, 0xbf, 0x37, 0x22,
+  0xbf, 0x07, 0x43, 0xb4, 0x30, 0xe1, 0x43, 0x8a, 0xcd, 0x03, 0x5d, 0x6d,
+  0xb9, 0x29, 0xd9, 0x84, 0xa7, 0xf5, 0x62, 0x63, 0x84, 0x86, 0xd6, 0x37,
+  0xbe, 0x6f, 0x67, 0xbb, 0xff, 0x62, 0x57, 0x39, 0x9d, 0x0c, 0x4d, 0xb2,
+  0x2a, 0x61, 0x3d, 0x1d, 0x9c, 0xef, 0x9a, 0x77, 0x20, 0xa0, 0x2f, 0xee,
+  0x1a, 0x72, 0x9d, 0xb0, 0x9d, 0xbf, 0x78, 0x13, 0x27, 0x07, 0x0a, 0x60,
+  0x11, 0x93, 0xf5, 0x0f, 0x2e, 0xc9, 0xef, 0x6b, 0x24, 0x83, 0xfe, 0x9b,
+  0x90, 0xb4, 0x4b, 0x68, 0x81, 0xd0, 0xc2, 0xfa, 0xe0, 0x3f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            04:00:00:00:00:01:2f:4e:e1:42:f9
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+        Validity
+            Not Before: Apr 13 10:00:00 2011 GMT
+            Not After : Apr 13 10:00:00 2022 GMT
+        Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:dd:35:1d:f2:20:54:26:1a:d0:ef:a5:6f:81:76:
+                    59:70:dc:e7:f4:d4:03:24:1f:24:0e:9d:22:9f:d4:
+                    27:32:7a:2b:7c:ee:8b:e3:61:62:38:17:af:b4:4b:
+                    7a:9f:67:21:1c:2d:95:54:ba:79:ba:b6:c4:f2:0d:
+                    21:74:17:67:74:e2:b1:64:08:99:60:78:fb:67:c2:
+                    4b:f7:27:8d:6f:36:76:cf:31:8c:e5:f1:06:d7:dc:
+                    57:0e:5b:ac:ee:ce:2d:ab:aa:a9:70:2f:02:86:c8:
+                    b1:d0:08:07:95:ea:2a:ec:d1:9e:e4:36:5c:3b:a6:
+                    36:b5:43:8b:ab:f7:8e:3e:00:1b:ff:85:59:6b:62:
+                    01:8d:82:e8:4a:ba:38:b3:e0:c3:f4:6d:19:a7:ea:
+                    05:dd:84:67:c2:66:c7:24:02:73:5a:b5:ee:a4:19:
+                    d9:fc:00:ce:b6:a4:8d:df:7e:bd:5f:b2:3a:9d:84:
+                    31:4f:c8:63:0c:e4:d8:0d:52:a3:7e:01:1b:d4:67:
+                    a5:18:28:eb:01:a7:82:3c:d9:8e:1d:e5:47:0d:ba:
+                    8b:59:14:a3:1f:1f:4b:ea:e2:27:46:86:ce:9d:39:
+                    c4:66:41:a7:e2:15:23:6b:56:47:c1:ed:c5:53:e4:
+                    d4:80:1f:6b:fa:80:46:98:b2:09:a6:0f:95:be:66:
+                    88:93
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                5D:46:B2:8D:C4:4B:74:1C:BB:ED:F5:73:B6:3A:B7:38:8F:75:9E:7E
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.globalsign.com/repository/
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.globalsign.net/root.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+            X509v3 Authority Key Identifier: 
+                keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+    Signature Algorithm: sha1WithRSAEncryption
+         73:7a:ec:01:2c:17:22:91:9a:ca:b1:67:18:a2:ba:c8:05:89:
+         92:24:de:1f:b8:ab:44:9f:f7:40:55:65:f2:e0:f4:2e:c7:de:
+         b0:3f:99:15:1f:95:70:82:e9:9b:4a:64:24:20:16:f0:76:17:
+         d2:1b:fe:ac:fa:06:b4:77:cf:98:d8:2a:ec:57:15:d8:5e:4e:
+         dd:8b:96:e1:53:33:19:91:d5:84:6e:25:ef:0f:cb:ad:bf:db:
+         4b:6b:56:cc:b5:d4:40:3e:26:5e:b6:59:f4:c5:90:c9:09:c4:
+         84:df:bc:26:7d:82:e9:eb:f4:5b:fc:c8:15:de:09:18:45:86:
+         b3:8b:4d:c7:6b:35:27:9b:60:f6:a4:5a:2a:58:49:b1:d8:35:
+         43:c6:32:bb:5e:3b:c4:4a:21:c1:a0:3b:5e:c1:23:a9:ce:db:
+         d5:ba:fe:5d:6d:fd:00:7e:fa:f1:94:37:61:b9:00:39:66:96:
+         a9:9c:b4:1e:11:ef:55:d8:b4:d8:b0:c4:a5:ae:32:0a:2f:f8:
+         2d:f4:a2:a7:ff:36:d3:5e:63:8b:4e:12:f7:b5:28:80:75:ee:
+         94:2f:70:a0:56:77:39:aa:39:97:17:fc:00:f3:cf:66:e7:a2:
+         71:92:ab:05:9b:73:2e:7a:e7:e7:21:59:09:8d:30:a1:ac:5c:
+         ca:19:7a:f8
+-----BEGIN CERTIFICATE-----
+MIIEizCCA3OgAwIBAgILBAAAAAABL07hQvkwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMTMwMQYDVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBW
+YWxpZGF0aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDdNR3yIFQmGtDvpW+Bdllw3Of01AMkHyQOnSKf1Ccyeit87ovjYWI4F6+0S3qf
+ZyEcLZVUunm6tsTyDSF0F2d04rFkCJlgePtnwkv3J41vNnbPMYzl8QbX3FcOW6zu
+zi2rqqlwLwKGyLHQCAeV6irs0Z7kNlw7pja1Q4ur944+ABv/hVlrYgGNguhKujiz
+4MP0bRmn6gXdhGfCZsckAnNate6kGdn8AM62pI3ffr1fsjqdhDFPyGMM5NgNUqN+
+ARvUZ6UYKOsBp4I82Y4d5UcNuotZFKMfH0vq4idGhs6dOcRmQafiFSNrVkfB7cVT
+5NSAH2v6gEaYsgmmD5W+ZoiTAgMBAAGjggFQMIIBTDAOBgNVHQ8BAf8EBAMCAQYw
+EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUXUayjcRLdBy77fVztjq3OI91
+nn4wRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3
+Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0
+dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAv
+MC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEw
+KQYDVR0lBCIwIAYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDMB8GA1Ud
+IwQYMBaAFGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBz
+euwBLBcikZrKsWcYorrIBYmSJN4fuKtEn/dAVWXy4PQux96wP5kVH5VwgumbSmQk
+IBbwdhfSG/6s+ga0d8+Y2CrsVxXYXk7di5bhUzMZkdWEbiXvD8utv9tLa1bMtdRA
+PiZetln0xZDJCcSE37wmfYLp6/Rb/MgV3gkYRYazi03HazUnm2D2pFoqWEmx2DVD
+xjK7XjvESiHBoDtewSOpztvVuv5dbf0AfvrxlDdhuQA5ZpapnLQeEe9V2LTYsMSl
+rjIKL/gt9KKn/zbTXmOLThL3tSiAde6UL3CgVnc5qjmXF/wA889m56JxkqsFm3Mu
+eufnIVkJjTChrFzKGXr4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert39[] = {
+  0x30, 0x82, 0x04, 0x8b, 0x30, 0x82, 0x03, 0x73, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+  0x42, 0xf9, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+  0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+  0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+  0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x5d, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+  0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+  0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+  0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x47,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72,
+  0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41,
+  0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xdd, 0x35, 0x1d, 0xf2, 0x20, 0x54, 0x26, 0x1a, 0xd0, 0xef,
+  0xa5, 0x6f, 0x81, 0x76, 0x59, 0x70, 0xdc, 0xe7, 0xf4, 0xd4, 0x03, 0x24,
+  0x1f, 0x24, 0x0e, 0x9d, 0x22, 0x9f, 0xd4, 0x27, 0x32, 0x7a, 0x2b, 0x7c,
+  0xee, 0x8b, 0xe3, 0x61, 0x62, 0x38, 0x17, 0xaf, 0xb4, 0x4b, 0x7a, 0x9f,
+  0x67, 0x21, 0x1c, 0x2d, 0x95, 0x54, 0xba, 0x79, 0xba, 0xb6, 0xc4, 0xf2,
+  0x0d, 0x21, 0x74, 0x17, 0x67, 0x74, 0xe2, 0xb1, 0x64, 0x08, 0x99, 0x60,
+  0x78, 0xfb, 0x67, 0xc2, 0x4b, 0xf7, 0x27, 0x8d, 0x6f, 0x36, 0x76, 0xcf,
+  0x31, 0x8c, 0xe5, 0xf1, 0x06, 0xd7, 0xdc, 0x57, 0x0e, 0x5b, 0xac, 0xee,
+  0xce, 0x2d, 0xab, 0xaa, 0xa9, 0x70, 0x2f, 0x02, 0x86, 0xc8, 0xb1, 0xd0,
+  0x08, 0x07, 0x95, 0xea, 0x2a, 0xec, 0xd1, 0x9e, 0xe4, 0x36, 0x5c, 0x3b,
+  0xa6, 0x36, 0xb5, 0x43, 0x8b, 0xab, 0xf7, 0x8e, 0x3e, 0x00, 0x1b, 0xff,
+  0x85, 0x59, 0x6b, 0x62, 0x01, 0x8d, 0x82, 0xe8, 0x4a, 0xba, 0x38, 0xb3,
+  0xe0, 0xc3, 0xf4, 0x6d, 0x19, 0xa7, 0xea, 0x05, 0xdd, 0x84, 0x67, 0xc2,
+  0x66, 0xc7, 0x24, 0x02, 0x73, 0x5a, 0xb5, 0xee, 0xa4, 0x19, 0xd9, 0xfc,
+  0x00, 0xce, 0xb6, 0xa4, 0x8d, 0xdf, 0x7e, 0xbd, 0x5f, 0xb2, 0x3a, 0x9d,
+  0x84, 0x31, 0x4f, 0xc8, 0x63, 0x0c, 0xe4, 0xd8, 0x0d, 0x52, 0xa3, 0x7e,
+  0x01, 0x1b, 0xd4, 0x67, 0xa5, 0x18, 0x28, 0xeb, 0x01, 0xa7, 0x82, 0x3c,
+  0xd9, 0x8e, 0x1d, 0xe5, 0x47, 0x0d, 0xba, 0x8b, 0x59, 0x14, 0xa3, 0x1f,
+  0x1f, 0x4b, 0xea, 0xe2, 0x27, 0x46, 0x86, 0xce, 0x9d, 0x39, 0xc4, 0x66,
+  0x41, 0xa7, 0xe2, 0x15, 0x23, 0x6b, 0x56, 0x47, 0xc1, 0xed, 0xc5, 0x53,
+  0xe4, 0xd4, 0x80, 0x1f, 0x6b, 0xfa, 0x80, 0x46, 0x98, 0xb2, 0x09, 0xa6,
+  0x0f, 0x95, 0xbe, 0x66, 0x88, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x50, 0x30, 0x82, 0x01, 0x4c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0x46, 0xb2, 0x8d, 0xc4, 0x4b,
+  0x74, 0x1c, 0xbb, 0xed, 0xf5, 0x73, 0xb6, 0x3a, 0xb7, 0x38, 0x8f, 0x75,
+  0x9e, 0x7e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30,
+  0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30,
+  0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+  0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+  0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+  0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+  0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30,
+  0x29, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x22, 0x30, 0x20, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
+  0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45,
+  0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff,
+  0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x73,
+  0x7a, 0xec, 0x01, 0x2c, 0x17, 0x22, 0x91, 0x9a, 0xca, 0xb1, 0x67, 0x18,
+  0xa2, 0xba, 0xc8, 0x05, 0x89, 0x92, 0x24, 0xde, 0x1f, 0xb8, 0xab, 0x44,
+  0x9f, 0xf7, 0x40, 0x55, 0x65, 0xf2, 0xe0, 0xf4, 0x2e, 0xc7, 0xde, 0xb0,
+  0x3f, 0x99, 0x15, 0x1f, 0x95, 0x70, 0x82, 0xe9, 0x9b, 0x4a, 0x64, 0x24,
+  0x20, 0x16, 0xf0, 0x76, 0x17, 0xd2, 0x1b, 0xfe, 0xac, 0xfa, 0x06, 0xb4,
+  0x77, 0xcf, 0x98, 0xd8, 0x2a, 0xec, 0x57, 0x15, 0xd8, 0x5e, 0x4e, 0xdd,
+  0x8b, 0x96, 0xe1, 0x53, 0x33, 0x19, 0x91, 0xd5, 0x84, 0x6e, 0x25, 0xef,
+  0x0f, 0xcb, 0xad, 0xbf, 0xdb, 0x4b, 0x6b, 0x56, 0xcc, 0xb5, 0xd4, 0x40,
+  0x3e, 0x26, 0x5e, 0xb6, 0x59, 0xf4, 0xc5, 0x90, 0xc9, 0x09, 0xc4, 0x84,
+  0xdf, 0xbc, 0x26, 0x7d, 0x82, 0xe9, 0xeb, 0xf4, 0x5b, 0xfc, 0xc8, 0x15,
+  0xde, 0x09, 0x18, 0x45, 0x86, 0xb3, 0x8b, 0x4d, 0xc7, 0x6b, 0x35, 0x27,
+  0x9b, 0x60, 0xf6, 0xa4, 0x5a, 0x2a, 0x58, 0x49, 0xb1, 0xd8, 0x35, 0x43,
+  0xc6, 0x32, 0xbb, 0x5e, 0x3b, 0xc4, 0x4a, 0x21, 0xc1, 0xa0, 0x3b, 0x5e,
+  0xc1, 0x23, 0xa9, 0xce, 0xdb, 0xd5, 0xba, 0xfe, 0x5d, 0x6d, 0xfd, 0x00,
+  0x7e, 0xfa, 0xf1, 0x94, 0x37, 0x61, 0xb9, 0x00, 0x39, 0x66, 0x96, 0xa9,
+  0x9c, 0xb4, 0x1e, 0x11, 0xef, 0x55, 0xd8, 0xb4, 0xd8, 0xb0, 0xc4, 0xa5,
+  0xae, 0x32, 0x0a, 0x2f, 0xf8, 0x2d, 0xf4, 0xa2, 0xa7, 0xff, 0x36, 0xd3,
+  0x5e, 0x63, 0x8b, 0x4e, 0x12, 0xf7, 0xb5, 0x28, 0x80, 0x75, 0xee, 0x94,
+  0x2f, 0x70, 0xa0, 0x56, 0x77, 0x39, 0xaa, 0x39, 0x97, 0x17, 0xfc, 0x00,
+  0xf3, 0xcf, 0x66, 0xe7, 0xa2, 0x71, 0x92, 0xab, 0x05, 0x9b, 0x73, 0x2e,
+  0x7a, 0xe7, 0xe7, 0x21, 0x59, 0x09, 0x8d, 0x30, 0xa1, 0xac, 0x5c, 0xca,
+  0x19, 0x7a, 0xf8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 67109810 (0x40003b2)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Dec 15 20:32:00 2004 GMT
+            Not After : Dec 15 23:59:00 2014 GMT
+        Subject: C=IT, O=I.T. Telecom, OU=Servizi di certificazione, CN=I.T. Telecom Global CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c3:14:14:be:cd:a1:7e:89:2d:ab:8f:e5:ab:5c:
+                    2e:70:a1:e3:cb:08:2e:07:2c:a9:38:f8:9a:1b:3b:
+                    02:32:43:47:e7:fd:98:04:83:2a:23:34:8b:a4:3c:
+                    92:1b:95:fb:ae:bb:5a:70:c9:92:5e:86:09:64:c6:
+                    42:c2:f3:6c:3c:18:35:8b:5f:f1:c9:ed:3d:72:cb:
+                    4b:3f:ed:61:c3:5f:dd:2c:38:27:e9:73:1d:04:e9:
+                    35:c7:7f:4f:92:a9:c2:f6:b9:a9:6d:05:0d:5b:02:
+                    bf:c7:0c:a9:0d:a2:5f:15:48:30:79:b7:ab:77:48:
+                    51:13:83:1f:0e:72:07:4a:75:13:be:b0:90:3f:c6:
+                    8f:17:03:32:55:76:1f:2f:3b:c1:ee:53:35:1d:33:
+                    86:25:6c:81:1b:eb:10:fe:d2:ab:b5:1b:3c:38:fd:
+                    90:71:02:70:3b:09:3a:c7:71:4a:c2:51:65:7c:f5:
+                    59:08:e1:4a:83:d4:5f:d0:10:1c:7b:f6:3c:95:24:
+                    99:59:2c:df:14:59:28:83:bf:f8:e8:52:83:c8:12:
+                    65:0f:95:a5:94:1c:02:d8:b7:aa:2d:6c:e5:59:53:
+                    65:f4:51:d7:48:96:2e:60:9c:59:9c:51:d0:97:b3:
+                    9e:38:91:ab:b3:7a:5e:b4:3b:bb:c8:62:3f:c0:b8:
+                    69:27
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                3E:81:72:D4:41:E6:9F:BC:B0:37:DA:3F:68:0E:7A:86:0B:EF:55:74
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://www.public-trust.com/CPS/OmniRoot.html
+                Policy: 1.3.76.12.1.1.3
+                  CPS: https://www.tipki.com/GlobalCA/CPS
+
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 Key Usage: critical
+                Digital Signature, Non Repudiation, Key Encipherment, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+    Signature Algorithm: sha1WithRSAEncryption
+         84:5b:69:ea:0c:c7:1e:de:b2:ca:fe:e8:26:ae:36:94:00:09:
+         25:69:07:93:e3:c3:96:4b:97:4c:a6:79:38:d2:df:dc:41:7b:
+         d8:61:70:ec:c0:36:8c:8c:b1:65:ac:d6:e4:32:b9:7c:3c:04:
+         79:72:af:87:0a:58:19:25:f7:25:b7:1d:49:c9:8b:33:54:43:
+         c4:8b:09:26:1c:a9:0c:50:e3:df:92:c5:68:6c:05:2d:c6:d7:
+         23:13:9e:bf:46:36:10:8d:f1:27:c1:74:a8:f5:0f:c6:b8:e2:
+         91:3c:2d:4c:fb:9d:da:1e:e4:a5:87:80:a6:ce:43:b7:14:7d:
+         9f:38
+-----BEGIN CERTIFICATE-----
+MIIEizCCA/SgAwIBAgIEBAADsjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA0MTIxNTIwMzIwMFoXDTE0MTIxNTIzNTkwMFowaTELMAkG
+A1UEBhMCSVQxFTATBgNVBAoTDEkuVC4gVGVsZWNvbTEiMCAGA1UECxMZU2Vydml6
+aSBkaSBjZXJ0aWZpY2F6aW9uZTEfMB0GA1UEAxMWSS5ULiBUZWxlY29tIEdsb2Jh
+bCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMUFL7NoX6JLauP
+5atcLnCh48sILgcsqTj4mhs7AjJDR+f9mASDKiM0i6Q8khuV+667WnDJkl6GCWTG
+QsLzbDwYNYtf8cntPXLLSz/tYcNf3Sw4J+lzHQTpNcd/T5Kpwva5qW0FDVsCv8cM
+qQ2iXxVIMHm3q3dIURODHw5yB0p1E76wkD/GjxcDMlV2Hy87we5TNR0zhiVsgRvr
+EP7Sq7UbPDj9kHECcDsJOsdxSsJRZXz1WQjhSoPUX9AQHHv2PJUkmVks3xRZKIO/
++OhSg8gSZQ+VpZQcAti3qi1s5VlTZfRR10iWLmCcWZxR0JeznjiRq7N6XrQ7u8hi
+P8C4aScCAwEAAaOCAa4wggGqMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cu
+cHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0O
+BBYEFD6BctRB5p+8sDfaP2gOeoYL71V0MIGRBgNVHSAEgYkwgYYwSAYJKwYBBAGx
+PgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9D
+UFMvT21uaVJvb3QuaHRtbDA6BgYrTAwBAQMwMDAuBggrBgEFBQcCARYiaHR0cHM6
+Ly93d3cudGlwa2kuY29tL0dsb2JhbENBL0NQUzCBiQYDVR0jBIGBMH+heaR3MHUx
+CzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsT
+HkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5
+YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMA4GA1UdDwEB/wQEAwIB5jASBgNVHRMB
+Af8ECDAGAQH/AgEBMA0GCSqGSIb3DQEBBQUAA4GBAIRbaeoMxx7essr+6CauNpQA
+CSVpB5Pjw5ZLl0ymeTjS39xBe9hhcOzANoyMsWWs1uQyuXw8BHlyr4cKWBkl9yW3
+HUnJizNUQ8SLCSYcqQxQ49+SxWhsBS3G1yMTnr9GNhCN8SfBdKj1D8a44pE8LUz7
+ndoe5KWHgKbOQ7cUfZ84
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert40[] = {
+  0x30, 0x82, 0x04, 0x8b, 0x30, 0x82, 0x03, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x04, 0x00, 0x03, 0xb2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x30, 0x34, 0x31, 0x32, 0x31, 0x35, 0x32, 0x30, 0x33, 0x32, 0x30,
+  0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x32, 0x31, 0x35, 0x32, 0x33,
+  0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x54, 0x31, 0x15, 0x30, 0x13,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x49, 0x2e, 0x54, 0x2e, 0x20,
+  0x54, 0x65, 0x6c, 0x65, 0x63, 0x6f, 0x6d, 0x31, 0x22, 0x30, 0x20, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x19, 0x53, 0x65, 0x72, 0x76, 0x69, 0x7a,
+  0x69, 0x20, 0x64, 0x69, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x7a, 0x69, 0x6f, 0x6e, 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x49, 0x2e, 0x54, 0x2e, 0x20, 0x54,
+  0x65, 0x6c, 0x65, 0x63, 0x6f, 0x6d, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+  0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xc3, 0x14, 0x14, 0xbe, 0xcd, 0xa1, 0x7e, 0x89, 0x2d, 0xab, 0x8f,
+  0xe5, 0xab, 0x5c, 0x2e, 0x70, 0xa1, 0xe3, 0xcb, 0x08, 0x2e, 0x07, 0x2c,
+  0xa9, 0x38, 0xf8, 0x9a, 0x1b, 0x3b, 0x02, 0x32, 0x43, 0x47, 0xe7, 0xfd,
+  0x98, 0x04, 0x83, 0x2a, 0x23, 0x34, 0x8b, 0xa4, 0x3c, 0x92, 0x1b, 0x95,
+  0xfb, 0xae, 0xbb, 0x5a, 0x70, 0xc9, 0x92, 0x5e, 0x86, 0x09, 0x64, 0xc6,
+  0x42, 0xc2, 0xf3, 0x6c, 0x3c, 0x18, 0x35, 0x8b, 0x5f, 0xf1, 0xc9, 0xed,
+  0x3d, 0x72, 0xcb, 0x4b, 0x3f, 0xed, 0x61, 0xc3, 0x5f, 0xdd, 0x2c, 0x38,
+  0x27, 0xe9, 0x73, 0x1d, 0x04, 0xe9, 0x35, 0xc7, 0x7f, 0x4f, 0x92, 0xa9,
+  0xc2, 0xf6, 0xb9, 0xa9, 0x6d, 0x05, 0x0d, 0x5b, 0x02, 0xbf, 0xc7, 0x0c,
+  0xa9, 0x0d, 0xa2, 0x5f, 0x15, 0x48, 0x30, 0x79, 0xb7, 0xab, 0x77, 0x48,
+  0x51, 0x13, 0x83, 0x1f, 0x0e, 0x72, 0x07, 0x4a, 0x75, 0x13, 0xbe, 0xb0,
+  0x90, 0x3f, 0xc6, 0x8f, 0x17, 0x03, 0x32, 0x55, 0x76, 0x1f, 0x2f, 0x3b,
+  0xc1, 0xee, 0x53, 0x35, 0x1d, 0x33, 0x86, 0x25, 0x6c, 0x81, 0x1b, 0xeb,
+  0x10, 0xfe, 0xd2, 0xab, 0xb5, 0x1b, 0x3c, 0x38, 0xfd, 0x90, 0x71, 0x02,
+  0x70, 0x3b, 0x09, 0x3a, 0xc7, 0x71, 0x4a, 0xc2, 0x51, 0x65, 0x7c, 0xf5,
+  0x59, 0x08, 0xe1, 0x4a, 0x83, 0xd4, 0x5f, 0xd0, 0x10, 0x1c, 0x7b, 0xf6,
+  0x3c, 0x95, 0x24, 0x99, 0x59, 0x2c, 0xdf, 0x14, 0x59, 0x28, 0x83, 0xbf,
+  0xf8, 0xe8, 0x52, 0x83, 0xc8, 0x12, 0x65, 0x0f, 0x95, 0xa5, 0x94, 0x1c,
+  0x02, 0xd8, 0xb7, 0xaa, 0x2d, 0x6c, 0xe5, 0x59, 0x53, 0x65, 0xf4, 0x51,
+  0xd7, 0x48, 0x96, 0x2e, 0x60, 0x9c, 0x59, 0x9c, 0x51, 0xd0, 0x97, 0xb3,
+  0x9e, 0x38, 0x91, 0xab, 0xb3, 0x7a, 0x5e, 0xb4, 0x3b, 0xbb, 0xc8, 0x62,
+  0x3f, 0xc0, 0xb8, 0x69, 0x27, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0xae, 0x30, 0x82, 0x01, 0xaa, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+  0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+  0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+  0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+  0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0x3e, 0x81, 0x72, 0xd4, 0x41, 0xe6, 0x9f, 0xbc,
+  0xb0, 0x37, 0xda, 0x3f, 0x68, 0x0e, 0x7a, 0x86, 0x0b, 0xef, 0x55, 0x74,
+  0x30, 0x81, 0x91, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x81, 0x89, 0x30,
+  0x81, 0x86, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1,
+  0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+  0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43,
+  0x50, 0x53, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e,
+  0x68, 0x74, 0x6d, 0x6c, 0x30, 0x3a, 0x06, 0x06, 0x2b, 0x4c, 0x0c, 0x01,
+  0x01, 0x03, 0x30, 0x30, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x01, 0x16, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x69, 0x70, 0x6b, 0x69, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x43, 0x41,
+  0x2f, 0x43, 0x50, 0x53, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+  0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+  0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79,
+  0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5,
+  0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+  0x03, 0x02, 0x01, 0xe6, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0x5b, 0x69, 0xea, 0x0c,
+  0xc7, 0x1e, 0xde, 0xb2, 0xca, 0xfe, 0xe8, 0x26, 0xae, 0x36, 0x94, 0x00,
+  0x09, 0x25, 0x69, 0x07, 0x93, 0xe3, 0xc3, 0x96, 0x4b, 0x97, 0x4c, 0xa6,
+  0x79, 0x38, 0xd2, 0xdf, 0xdc, 0x41, 0x7b, 0xd8, 0x61, 0x70, 0xec, 0xc0,
+  0x36, 0x8c, 0x8c, 0xb1, 0x65, 0xac, 0xd6, 0xe4, 0x32, 0xb9, 0x7c, 0x3c,
+  0x04, 0x79, 0x72, 0xaf, 0x87, 0x0a, 0x58, 0x19, 0x25, 0xf7, 0x25, 0xb7,
+  0x1d, 0x49, 0xc9, 0x8b, 0x33, 0x54, 0x43, 0xc4, 0x8b, 0x09, 0x26, 0x1c,
+  0xa9, 0x0c, 0x50, 0xe3, 0xdf, 0x92, 0xc5, 0x68, 0x6c, 0x05, 0x2d, 0xc6,
+  0xd7, 0x23, 0x13, 0x9e, 0xbf, 0x46, 0x36, 0x10, 0x8d, 0xf1, 0x27, 0xc1,
+  0x74, 0xa8, 0xf5, 0x0f, 0xc6, 0xb8, 0xe2, 0x91, 0x3c, 0x2d, 0x4c, 0xfb,
+  0x9d, 0xda, 0x1e, 0xe4, 0xa5, 0x87, 0x80, 0xa6, 0xce, 0x43, 0xb7, 0x14,
+  0x7d, 0x9f, 0x38,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            76:10:12:8a:17:b6:82:bb:3a:1f:9d:1a:9a:35:c0:92
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+        Validity
+            Not Before: Feb 18 00:00:00 2010 GMT
+            Not After : Feb 17 23:59:59 2020 GMT
+        Subject: C=US, O=Thawte, Inc., OU=Domain Validated SSL, CN=Thawte DV SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:cb:98:c9:36:3f:d2:9c:d8:16:07:d4:49:63:f9:
+                    83:b0:e8:02:2d:cc:5c:5a:74:97:a6:13:ef:13:13:
+                    de:05:7c:a7:e6:ca:00:23:da:39:f9:ef:13:cf:52:
+                    c5:af:9a:e3:ca:be:f3:82:d9:8b:3d:aa:e1:cc:ae:
+                    88:50:66:a3:2d:ec:61:14:75:49:ab:0e:24:f1:ac:
+                    44:5b:0b:28:a2:33:20:76:1e:06:60:6a:67:05:71:
+                    8b:ba:66:62:16:7a:b3:6d:0d:c7:d0:94:40:c6:8c:
+                    3d:1e:92:0c:62:34:0d:44:89:d5:f7:89:fe:29:ed:
+                    18:8f:f6:9b:2b:08:f7:6a:ab:d8:48:97:5a:f4:9f:
+                    ed:0c:75:52:22:f7:d5:5e:84:00:9f:c0:4a:0d:31:
+                    77:4c:64:d0:12:e6:0f:3a:f0:a1:c0:d5:5c:1d:e7:
+                    5f:2d:c2:f7:d6:36:18:d9:95:6e:44:4e:c9:58:14:
+                    4d:b6:8e:bb:cd:de:62:1e:fa:5b:b5:bd:18:2b:98:
+                    ac:ac:93:3f:50:5a:f5:14:0b:a2:cf:b6:f3:9e:4f:
+                    5a:cd:5a:c3:36:23:da:1a:af:b0:4d:d6:4a:22:03:
+                    8f:43:02:19:bd:ea:ac:dd:c4:7a:35:32:14:f1:72:
+                    2e:08:55:40:0c:f4:07:41:41:af:38:37:84:29:42:
+                    b2:55
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.thawte.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.thawte.com/ThawtePCA.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-11
+            X509v3 Subject Key Identifier: 
+                AB:44:E4:5D:EC:83:C7:D9:C0:85:9F:F7:E1:C6:97:90:B0:8C:3F:98
+            X509v3 Authority Key Identifier: 
+                keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+    Signature Algorithm: sha1WithRSAEncryption
+         04:ba:fb:ac:bb:fc:4b:54:11:a3:2d:88:b3:3c:bd:00:6d:8a:
+         1a:b6:8d:c4:c1:83:f8:c7:53:2a:c1:32:6e:3a:81:a1:54:7d:
+         da:1a:3f:3a:45:4f:36:e7:42:b0:0a:42:85:97:a0:ac:fb:e5:
+         87:a7:83:4f:e8:b1:b7:9b:58:65:6e:26:80:0b:92:4d:47:55:
+         b9:61:16:51:65:e9:2b:f1:68:d9:58:b8:03:81:d1:b7:66:1c:
+         d3:bc:c5:a6:7b:5f:3e:c5:38:46:76:e7:75:b4:a0:0c:4b:ce:
+         a2:c2:a9:c1:cc:36:73:7b:fb:b9:24:24:a0:5e:a7:f6:fa:bb:
+         0c:28:43:9e:1d:f0:4e:f0:3f:d8:24:b0:21:dc:6d:2d:ee:bf:
+         5a:3b:fa:88:9c:74:6c:af:21:dd:92:ec:c3:15:ef:94:75:26:
+         46:d6:a6:3f:bf:66:48:aa:1d:ef:dd:27:e6:b7:51:89:38:7d:
+         13:84:0c:40:fc:d0:b5:f1:e0:db:f9:4f:2f:40:1c:b4:8e:47:
+         22:61:b8:4c:96:de:f0:5f:11:7e:4f:11:d9:ec:50:47:22:0e:
+         c5:1d:e2:64:49:e7:68:63:45:3a:8a:d9:71:f4:5e:f1:6e:b7:
+         14:4d:3e:6f:14:1e:dc:52:fe:bc:df:0c:bd:29:3f:76:fb:11:
+         5f:68:68:15
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIQdhASihe2grs6H50amjXAkjANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjE4MDAwMDAwWhcNMjAw
+MjE3MjM1OTU5WjBeMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMu
+MR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEZMBcGA1UEAxMQVGhhd3Rl
+IERWIFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMuYyTY/
+0pzYFgfUSWP5g7DoAi3MXFp0l6YT7xMT3gV8p+bKACPaOfnvE89Sxa+a48q+84LZ
+iz2q4cyuiFBmoy3sYRR1SasOJPGsRFsLKKIzIHYeBmBqZwVxi7pmYhZ6s20Nx9CU
+QMaMPR6SDGI0DUSJ1feJ/intGI/2mysI92qr2EiXWvSf7Qx1UiL31V6EAJ/ASg0x
+d0xk0BLmDzrwocDVXB3nXy3C99Y2GNmVbkROyVgUTbaOu83eYh76W7W9GCuYrKyT
+P1Ba9RQLos+2855PWs1awzYj2hqvsE3WSiIDj0MCGb3qrN3EejUyFPFyLghVQAz0
+B0FBrzg3hClCslUCAwEAAaOB/DCB+TAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUH
+MAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgwBgEB/wIBADA0
+BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vY3JsLnRoYXd0ZS5jb20vVGhhd3RlUENB
+LmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNVBAMTEVZl
+cmlTaWduTVBLSS0yLTExMB0GA1UdDgQWBBSrRORd7IPH2cCFn/fhxpeQsIw/mDAf
+BgNVHSMEGDAWgBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOC
+AQEABLr7rLv8S1QRoy2Iszy9AG2KGraNxMGD+MdTKsEybjqBoVR92ho/OkVPNudC
+sApChZegrPvlh6eDT+ixt5tYZW4mgAuSTUdVuWEWUWXpK/Fo2Vi4A4HRt2Yc07zF
+pntfPsU4RnbndbSgDEvOosKpwcw2c3v7uSQkoF6n9vq7DChDnh3wTvA/2CSwIdxt
+Le6/Wjv6iJx0bK8h3ZLswxXvlHUmRtamP79mSKod790n5rdRiTh9E4QMQPzQtfHg
+2/lPL0ActI5HImG4TJbe8F8Rfk8R2exQRyIOxR3iZEnnaGNFOorZcfRe8W63FE0+
+bxQe3FL+vN8MvSk/dvsRX2hoFQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert41[] = {
+  0x30, 0x82, 0x04, 0x8f, 0x30, 0x82, 0x03, 0x77, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x76, 0x10, 0x12, 0x8a, 0x17, 0xb6, 0x82, 0xbb, 0x3a,
+  0x1f, 0x9d, 0x1a, 0x9a, 0x35, 0xc0, 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+  0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+  0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+  0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x31, 0x38,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+  0x32, 0x31, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x5e,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x14, 0x44,
+  0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61,
+  0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, 0x19, 0x30, 0x17, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65,
+  0x20, 0x44, 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82,
+  0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+  0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcb, 0x98, 0xc9, 0x36, 0x3f,
+  0xd2, 0x9c, 0xd8, 0x16, 0x07, 0xd4, 0x49, 0x63, 0xf9, 0x83, 0xb0, 0xe8,
+  0x02, 0x2d, 0xcc, 0x5c, 0x5a, 0x74, 0x97, 0xa6, 0x13, 0xef, 0x13, 0x13,
+  0xde, 0x05, 0x7c, 0xa7, 0xe6, 0xca, 0x00, 0x23, 0xda, 0x39, 0xf9, 0xef,
+  0x13, 0xcf, 0x52, 0xc5, 0xaf, 0x9a, 0xe3, 0xca, 0xbe, 0xf3, 0x82, 0xd9,
+  0x8b, 0x3d, 0xaa, 0xe1, 0xcc, 0xae, 0x88, 0x50, 0x66, 0xa3, 0x2d, 0xec,
+  0x61, 0x14, 0x75, 0x49, 0xab, 0x0e, 0x24, 0xf1, 0xac, 0x44, 0x5b, 0x0b,
+  0x28, 0xa2, 0x33, 0x20, 0x76, 0x1e, 0x06, 0x60, 0x6a, 0x67, 0x05, 0x71,
+  0x8b, 0xba, 0x66, 0x62, 0x16, 0x7a, 0xb3, 0x6d, 0x0d, 0xc7, 0xd0, 0x94,
+  0x40, 0xc6, 0x8c, 0x3d, 0x1e, 0x92, 0x0c, 0x62, 0x34, 0x0d, 0x44, 0x89,
+  0xd5, 0xf7, 0x89, 0xfe, 0x29, 0xed, 0x18, 0x8f, 0xf6, 0x9b, 0x2b, 0x08,
+  0xf7, 0x6a, 0xab, 0xd8, 0x48, 0x97, 0x5a, 0xf4, 0x9f, 0xed, 0x0c, 0x75,
+  0x52, 0x22, 0xf7, 0xd5, 0x5e, 0x84, 0x00, 0x9f, 0xc0, 0x4a, 0x0d, 0x31,
+  0x77, 0x4c, 0x64, 0xd0, 0x12, 0xe6, 0x0f, 0x3a, 0xf0, 0xa1, 0xc0, 0xd5,
+  0x5c, 0x1d, 0xe7, 0x5f, 0x2d, 0xc2, 0xf7, 0xd6, 0x36, 0x18, 0xd9, 0x95,
+  0x6e, 0x44, 0x4e, 0xc9, 0x58, 0x14, 0x4d, 0xb6, 0x8e, 0xbb, 0xcd, 0xde,
+  0x62, 0x1e, 0xfa, 0x5b, 0xb5, 0xbd, 0x18, 0x2b, 0x98, 0xac, 0xac, 0x93,
+  0x3f, 0x50, 0x5a, 0xf5, 0x14, 0x0b, 0xa2, 0xcf, 0xb6, 0xf3, 0x9e, 0x4f,
+  0x5a, 0xcd, 0x5a, 0xc3, 0x36, 0x23, 0xda, 0x1a, 0xaf, 0xb0, 0x4d, 0xd6,
+  0x4a, 0x22, 0x03, 0x8f, 0x43, 0x02, 0x19, 0xbd, 0xea, 0xac, 0xdd, 0xc4,
+  0x7a, 0x35, 0x32, 0x14, 0xf1, 0x72, 0x2e, 0x08, 0x55, 0x40, 0x0c, 0xf4,
+  0x07, 0x41, 0x41, 0xaf, 0x38, 0x37, 0x84, 0x29, 0x42, 0xb2, 0x55, 0x02,
+  0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfc, 0x30, 0x81, 0xf9, 0x30, 0x32,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26,
+  0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f,
+  0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+  0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x34,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0,
+  0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03,
+  0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31,
+  0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32,
+  0x2d, 0x31, 0x31, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0xab, 0x44, 0xe4, 0x5d, 0xec, 0x83, 0xc7, 0xd9, 0xc0, 0x85,
+  0x9f, 0xf7, 0xe1, 0xc6, 0x97, 0x90, 0xb0, 0x8c, 0x3f, 0x98, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b,
+  0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a,
+  0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x04, 0xba, 0xfb, 0xac, 0xbb, 0xfc, 0x4b, 0x54, 0x11,
+  0xa3, 0x2d, 0x88, 0xb3, 0x3c, 0xbd, 0x00, 0x6d, 0x8a, 0x1a, 0xb6, 0x8d,
+  0xc4, 0xc1, 0x83, 0xf8, 0xc7, 0x53, 0x2a, 0xc1, 0x32, 0x6e, 0x3a, 0x81,
+  0xa1, 0x54, 0x7d, 0xda, 0x1a, 0x3f, 0x3a, 0x45, 0x4f, 0x36, 0xe7, 0x42,
+  0xb0, 0x0a, 0x42, 0x85, 0x97, 0xa0, 0xac, 0xfb, 0xe5, 0x87, 0xa7, 0x83,
+  0x4f, 0xe8, 0xb1, 0xb7, 0x9b, 0x58, 0x65, 0x6e, 0x26, 0x80, 0x0b, 0x92,
+  0x4d, 0x47, 0x55, 0xb9, 0x61, 0x16, 0x51, 0x65, 0xe9, 0x2b, 0xf1, 0x68,
+  0xd9, 0x58, 0xb8, 0x03, 0x81, 0xd1, 0xb7, 0x66, 0x1c, 0xd3, 0xbc, 0xc5,
+  0xa6, 0x7b, 0x5f, 0x3e, 0xc5, 0x38, 0x46, 0x76, 0xe7, 0x75, 0xb4, 0xa0,
+  0x0c, 0x4b, 0xce, 0xa2, 0xc2, 0xa9, 0xc1, 0xcc, 0x36, 0x73, 0x7b, 0xfb,
+  0xb9, 0x24, 0x24, 0xa0, 0x5e, 0xa7, 0xf6, 0xfa, 0xbb, 0x0c, 0x28, 0x43,
+  0x9e, 0x1d, 0xf0, 0x4e, 0xf0, 0x3f, 0xd8, 0x24, 0xb0, 0x21, 0xdc, 0x6d,
+  0x2d, 0xee, 0xbf, 0x5a, 0x3b, 0xfa, 0x88, 0x9c, 0x74, 0x6c, 0xaf, 0x21,
+  0xdd, 0x92, 0xec, 0xc3, 0x15, 0xef, 0x94, 0x75, 0x26, 0x46, 0xd6, 0xa6,
+  0x3f, 0xbf, 0x66, 0x48, 0xaa, 0x1d, 0xef, 0xdd, 0x27, 0xe6, 0xb7, 0x51,
+  0x89, 0x38, 0x7d, 0x13, 0x84, 0x0c, 0x40, 0xfc, 0xd0, 0xb5, 0xf1, 0xe0,
+  0xdb, 0xf9, 0x4f, 0x2f, 0x40, 0x1c, 0xb4, 0x8e, 0x47, 0x22, 0x61, 0xb8,
+  0x4c, 0x96, 0xde, 0xf0, 0x5f, 0x11, 0x7e, 0x4f, 0x11, 0xd9, 0xec, 0x50,
+  0x47, 0x22, 0x0e, 0xc5, 0x1d, 0xe2, 0x64, 0x49, 0xe7, 0x68, 0x63, 0x45,
+  0x3a, 0x8a, 0xd9, 0x71, 0xf4, 0x5e, 0xf1, 0x6e, 0xb7, 0x14, 0x4d, 0x3e,
+  0x6f, 0x14, 0x1e, 0xdc, 0x52, 0xfe, 0xbc, 0xdf, 0x0c, 0xbd, 0x29, 0x3f,
+  0x76, 0xfb, 0x11, 0x5f, 0x68, 0x68, 0x15,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            1b:09:3b:78:60:96:da:37:bb:a4:51:94:46:c8:96:78
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2021 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+                    4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+                    08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+                    2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+                    8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+                    a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+                    54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+                    d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+                    7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+                    bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+                    f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+                    ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+                    f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+                    21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+                    63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+                    ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+                    9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+                    25:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 Subject Key Identifier: 
+                7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         a3:cd:7d:1e:f7:c7:75:8d:48:e7:56:34:4c:00:90:75:a9:51:
+         a5:56:c1:6d:bc:fe:f5:53:22:e9:98:a2:ac:9a:7e:70:1e:b3:
+         8e:3b:45:e3:86:95:31:da:6d:4c:fb:34:50:80:96:cd:24:f2:
+         40:df:04:3f:e2:65:ce:34:22:61:15:ea:66:70:64:d2:f1:6e:
+         f3:ca:18:59:6a:41:46:7e:82:de:19:b0:70:31:56:69:0d:0c:
+         e6:1d:9d:71:58:dc:cc:de:62:f5:e1:7a:10:02:d8:7a:dc:3b:
+         fa:57:bd:c9:e9:8f:46:21:39:9f:51:65:4c:8e:3a:be:28:41:
+         70:1d
+-----BEGIN CERTIFICATE-----
+MIIEkDCCA/mgAwIBAgIQGwk7eGCW2je7pFGURsiWeDANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggFbMIIBVzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
+KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
+j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
+L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
+b2NzcC52ZXJpc2lnbi5jb20wDQYJKoZIhvcNAQEFBQADgYEAo819HvfHdY1I51Y0
+TACQdalRpVbBbbz+9VMi6ZiirJp+cB6zjjtF44aVMdptTPs0UICWzSTyQN8EP+Jl
+zjQiYRXqZnBk0vFu88oYWWpBRn6C3hmwcDFWaQ0M5h2dcVjczN5i9eF6EALYetw7
++le9yemPRiE5n1FlTI46vihBcB0=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert42[] = {
+  0x30, 0x82, 0x04, 0x90, 0x30, 0x82, 0x03, 0xf9, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x1b, 0x09, 0x3b, 0x78, 0x60, 0x96, 0xda, 0x37, 0xbb,
+  0xa4, 0x51, 0x94, 0x46, 0xc8, 0x96, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+  0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+  0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+  0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+  0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+  0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+  0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+  0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+  0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+  0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+  0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+  0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+  0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+  0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+  0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+  0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+  0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+  0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+  0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+  0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+  0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+  0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+  0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+  0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+  0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+  0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+  0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+  0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+  0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+  0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x5b, 0x30, 0x82, 0x01, 0x57, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+  0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+  0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+  0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+  0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+  0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f,
+  0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09,
+  0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30,
+  0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14,
+  0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80,
+  0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30,
+  0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+  0xa3, 0xcd, 0x7d, 0x1e, 0xf7, 0xc7, 0x75, 0x8d, 0x48, 0xe7, 0x56, 0x34,
+  0x4c, 0x00, 0x90, 0x75, 0xa9, 0x51, 0xa5, 0x56, 0xc1, 0x6d, 0xbc, 0xfe,
+  0xf5, 0x53, 0x22, 0xe9, 0x98, 0xa2, 0xac, 0x9a, 0x7e, 0x70, 0x1e, 0xb3,
+  0x8e, 0x3b, 0x45, 0xe3, 0x86, 0x95, 0x31, 0xda, 0x6d, 0x4c, 0xfb, 0x34,
+  0x50, 0x80, 0x96, 0xcd, 0x24, 0xf2, 0x40, 0xdf, 0x04, 0x3f, 0xe2, 0x65,
+  0xce, 0x34, 0x22, 0x61, 0x15, 0xea, 0x66, 0x70, 0x64, 0xd2, 0xf1, 0x6e,
+  0xf3, 0xca, 0x18, 0x59, 0x6a, 0x41, 0x46, 0x7e, 0x82, 0xde, 0x19, 0xb0,
+  0x70, 0x31, 0x56, 0x69, 0x0d, 0x0c, 0xe6, 0x1d, 0x9d, 0x71, 0x58, 0xdc,
+  0xcc, 0xde, 0x62, 0xf5, 0xe1, 0x7a, 0x10, 0x02, 0xd8, 0x7a, 0xdc, 0x3b,
+  0xfa, 0x57, 0xbd, 0xc9, 0xe9, 0x8f, 0x46, 0x21, 0x39, 0x9f, 0x51, 0x65,
+  0x4c, 0x8e, 0x3a, 0xbe, 0x28, 0x41, 0x70, 0x1d,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            3d:3a:05:26:09:b6:2e:e5:8c:36:29:38:63:54:e1:24
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Validity
+            Not Before: Dec  1 00:00:00 2006 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+                    98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+                    e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+                    b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+                    86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+                    da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+                    d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+                    7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+                    9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+                    30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+                    77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+                    02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+                    c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+                    00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+                    0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+                    2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+                    47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+                    26:f7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+            X509v3 Subject Key Identifier: 
+                0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         9e:cf:0c:29:ff:99:5f:85:24:63:19:5a:68:f5:e4:46:ce:53:
+         57:91:d6:25:fd:ea:ed:64:0f:73:da:aa:1c:25:d9:fb:ee:2c:
+         03:87:9d:8d:a9:7a:63:5c:50:a8:f7:64:8c:96:2c:ba:0b:f2:
+         7e:f9:74:29:c6:e5:4b:0b:50:30:af:c3:2e:3e:52:1a:fb:35:
+         66:59:12:62:e0:68:7a:b0:42:01:a9:16:c3:8a:fa:45:19:7a:
+         f2:e0:2b:bf:78:87:49:6f:7e:ff:d4:7c:bd:e1:8b:a5:7b:43:
+         9b:2c:42:cf:62:ef:63:1e:1c:85:d5:4c:b0:4a:91:4c:61:66:
+         5b:26:7e:25:e1:c7:15:c0:54:4b:c9:66:16:29:63:df:71:ab:
+         b6:07:92:fa:f3:4f:f2:31:d6:32:d0:4d:35:db:5b:89:b8:08:
+         e4:68:de:d8:47:cb:d7:5e:e8:16:b2:94:21:9c:6a:5b:bf:b4:
+         81:86:dd:c5:f2:a8:71:3e:dd:a7:4a:b5:fa:f8:6c:3b:34:9a:
+         9b:58:7d:4d:d4:d3:5b:53:23:6b:49:38:16:a1:98:9f:84:5e:
+         ab:ae:3f:ae:ce:7f:c8:17:e4:32:ab:c4:d3:2f:9a:90:31:c2:
+         92:53:96:ed:72:a7:fe:c4:da:39:29:51:68:ed:90:8d:97:8e:
+         fe:45:19:b7
+-----BEGIN CERTIFICATE-----
+MIIEmTCCA4GgAwIBAgIQPToFJgm2LuWMNik4Y1ThJDANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYxMjAxMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgTELMAkG
+A1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMH
+U2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNP
+TU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBANBAi4ty45Eb91HBG1QEmNOpv8Hmil07h/u7iM4N4y8/Bpbw
+oilQma7bO6FXsHRRcc3tQpFNQf6pyNhqhndEu1lml1BetNQscETP2jeVQmk8MMRx
+s1LwIU2h2Lo5fByeoySd8oMWmKoWfEObFVu3rjSR/tRiJhhGmj/rwfnxkFfrrHoN
+i9tyMGpm1eBGo3DcaNn/BEiJd9616ftnbUHpvDm9MtliAvGxqD1uN5ziL+LToiaL
+xrhVQ4jhIz6l0iQ5akerANShs6kl/g0/px2601HBC6TarDjvVVAkBWVGkzRPLY2t
+xtQhGdKOygVhcQdzR+WKGRK9BE3OTpylSKy7JvcCAwEAAaOB9DCB8TAfBgNVHSME
+GDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNVHQ4EFgQUC1jli8ZMFTekQKkw
+qSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0g
+BAowCDAGBgRVHSAAMHsGA1UdHwR0MHIwOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1VUTi1VU0VSRmlyc3QtSGFyZHdhcmUuY3JsMDagNKAyhjBodHRwOi8v
+Y3JsLmNvbW9kby5uZXQvVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5jcmwwDQYJKoZI
+hvcNAQEFBQADggEBAJ7PDCn/mV+FJGMZWmj15EbOU1eR1iX96u1kD3Paqhwl2fvu
+LAOHnY2pemNcUKj3ZIyWLLoL8n75dCnG5UsLUDCvwy4+Uhr7NWZZEmLgaHqwQgGp
+FsOK+kUZevLgK794h0lvfv/UfL3hi6V7Q5ssQs9i72MeHIXVTLBKkUxhZlsmfiXh
+xxXAVEvJZhYpY99xq7YHkvrzT/Ix1jLQTTXbW4m4CORo3thHy9de6BaylCGcalu/
+tIGG3cXyqHE+3adKtfr4bDs0mptYfU3U01tTI2tJOBahmJ+EXquuP67Of8gX5DKr
+xNMvmpAxwpJTlu1yp/7E2jkpUWjtkI2Xjv5FGbc=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert43[] = {
+  0x30, 0x82, 0x04, 0x99, 0x30, 0x82, 0x03, 0x81, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x3d, 0x3a, 0x05, 0x26, 0x09, 0xb6, 0x2e, 0xe5, 0x8c,
+  0x36, 0x29, 0x38, 0x63, 0x54, 0xe1, 0x24, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+  0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+  0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+  0x38, 0x33, 0x38, 0x5a, 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06,
+  0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19,
+  0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74,
+  0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65,
+  0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07,
+  0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f,
+  0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31,
+  0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f,
+  0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xd0, 0x40, 0x8b, 0x8b, 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1,
+  0x1b, 0x54, 0x04, 0x98, 0xd3, 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b,
+  0x87, 0xfb, 0xbb, 0x88, 0xce, 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0,
+  0xa2, 0x29, 0x50, 0x99, 0xae, 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51,
+  0x71, 0xcd, 0xed, 0x42, 0x91, 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a,
+  0x86, 0x77, 0x44, 0xbb, 0x59, 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c,
+  0x70, 0x44, 0xcf, 0xda, 0x37, 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71,
+  0xb3, 0x52, 0xf0, 0x21, 0x4d, 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e,
+  0xa3, 0x24, 0x9d, 0xf2, 0x83, 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b,
+  0x15, 0x5b, 0xb7, 0xae, 0x34, 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46,
+  0x9a, 0x3f, 0xeb, 0xc1, 0xf9, 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d,
+  0x8b, 0xdb, 0x72, 0x30, 0x6a, 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc,
+  0x68, 0xd9, 0xff, 0x04, 0x48, 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67,
+  0x6d, 0x41, 0xe9, 0xbc, 0x39, 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1,
+  0xa8, 0x3d, 0x6e, 0x37, 0x9c, 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b,
+  0xc6, 0xb8, 0x55, 0x43, 0x88, 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39,
+  0x6a, 0x47, 0xab, 0x00, 0xd4, 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f,
+  0xa7, 0x1d, 0xba, 0xd3, 0x51, 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef,
+  0x55, 0x50, 0x24, 0x05, 0x65, 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad,
+  0xc6, 0xd4, 0x21, 0x19, 0xd2, 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73,
+  0x47, 0xe5, 0x8a, 0x19, 0x12, 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5,
+  0x48, 0xac, 0xbb, 0x26, 0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+  0xf4, 0x30, 0x81, 0xf1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+  0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+  0x45, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30,
+  0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+  0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+  0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72, 0x30,
+  0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+  0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55,
+  0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72,
+  0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x36, 0xa0,
+  0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46,
+  0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72,
+  0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+  0x00, 0x9e, 0xcf, 0x0c, 0x29, 0xff, 0x99, 0x5f, 0x85, 0x24, 0x63, 0x19,
+  0x5a, 0x68, 0xf5, 0xe4, 0x46, 0xce, 0x53, 0x57, 0x91, 0xd6, 0x25, 0xfd,
+  0xea, 0xed, 0x64, 0x0f, 0x73, 0xda, 0xaa, 0x1c, 0x25, 0xd9, 0xfb, 0xee,
+  0x2c, 0x03, 0x87, 0x9d, 0x8d, 0xa9, 0x7a, 0x63, 0x5c, 0x50, 0xa8, 0xf7,
+  0x64, 0x8c, 0x96, 0x2c, 0xba, 0x0b, 0xf2, 0x7e, 0xf9, 0x74, 0x29, 0xc6,
+  0xe5, 0x4b, 0x0b, 0x50, 0x30, 0xaf, 0xc3, 0x2e, 0x3e, 0x52, 0x1a, 0xfb,
+  0x35, 0x66, 0x59, 0x12, 0x62, 0xe0, 0x68, 0x7a, 0xb0, 0x42, 0x01, 0xa9,
+  0x16, 0xc3, 0x8a, 0xfa, 0x45, 0x19, 0x7a, 0xf2, 0xe0, 0x2b, 0xbf, 0x78,
+  0x87, 0x49, 0x6f, 0x7e, 0xff, 0xd4, 0x7c, 0xbd, 0xe1, 0x8b, 0xa5, 0x7b,
+  0x43, 0x9b, 0x2c, 0x42, 0xcf, 0x62, 0xef, 0x63, 0x1e, 0x1c, 0x85, 0xd5,
+  0x4c, 0xb0, 0x4a, 0x91, 0x4c, 0x61, 0x66, 0x5b, 0x26, 0x7e, 0x25, 0xe1,
+  0xc7, 0x15, 0xc0, 0x54, 0x4b, 0xc9, 0x66, 0x16, 0x29, 0x63, 0xdf, 0x71,
+  0xab, 0xb6, 0x07, 0x92, 0xfa, 0xf3, 0x4f, 0xf2, 0x31, 0xd6, 0x32, 0xd0,
+  0x4d, 0x35, 0xdb, 0x5b, 0x89, 0xb8, 0x08, 0xe4, 0x68, 0xde, 0xd8, 0x47,
+  0xcb, 0xd7, 0x5e, 0xe8, 0x16, 0xb2, 0x94, 0x21, 0x9c, 0x6a, 0x5b, 0xbf,
+  0xb4, 0x81, 0x86, 0xdd, 0xc5, 0xf2, 0xa8, 0x71, 0x3e, 0xdd, 0xa7, 0x4a,
+  0xb5, 0xfa, 0xf8, 0x6c, 0x3b, 0x34, 0x9a, 0x9b, 0x58, 0x7d, 0x4d, 0xd4,
+  0xd3, 0x5b, 0x53, 0x23, 0x6b, 0x49, 0x38, 0x16, 0xa1, 0x98, 0x9f, 0x84,
+  0x5e, 0xab, 0xae, 0x3f, 0xae, 0xce, 0x7f, 0xc8, 0x17, 0xe4, 0x32, 0xab,
+  0xc4, 0xd3, 0x2f, 0x9a, 0x90, 0x31, 0xc2, 0x92, 0x53, 0x96, 0xed, 0x72,
+  0xa7, 0xfe, 0xc4, 0xda, 0x39, 0x29, 0x51, 0x68, 0xed, 0x90, 0x8d, 0x97,
+  0x8e, 0xfe, 0x45, 0x19, 0xb7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1116155212 (0x42872d4c)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: Jan  5 19:20:39 2007 GMT
+            Not After : Jan  5 19:50:39 2017 GMT
+        Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:95:b6:43:42:fa:c6:6d:2a:6f:48:df:94:4c:
+                    39:57:05:ee:c3:79:11:41:68:36:ed:ec:fe:9a:01:
+                    8f:a1:38:28:fc:f7:10:46:66:2e:4d:1e:1a:b1:1a:
+                    4e:c6:d1:c0:95:88:b0:c9:ff:31:8b:33:03:db:b7:
+                    83:7b:3e:20:84:5e:ed:b2:56:28:a7:f8:e0:b9:40:
+                    71:37:c5:cb:47:0e:97:2a:68:c0:22:95:62:15:db:
+                    47:d9:f5:d0:2b:ff:82:4b:c9:ad:3e:de:4c:db:90:
+                    80:50:3f:09:8a:84:00:ec:30:0a:3d:18:cd:fb:fd:
+                    2a:59:9a:23:95:17:2c:45:9e:1f:6e:43:79:6d:0c:
+                    5c:98:fe:48:a7:c5:23:47:5c:5e:fd:6e:e7:1e:b4:
+                    f6:68:45:d1:86:83:5b:a2:8a:8d:b1:e3:29:80:fe:
+                    25:71:88:ad:be:bc:8f:ac:52:96:4b:aa:51:8d:e4:
+                    13:31:19:e8:4e:4d:9f:db:ac:b3:6a:d5:bc:39:54:
+                    71:ca:7a:7a:7f:90:dd:7d:1d:80:d9:81:bb:59:26:
+                    c2:11:fe:e6:93:e2:f7:80:e4:65:fb:34:37:0e:29:
+                    80:70:4d:af:38:86:2e:9e:7f:57:af:9e:17:ae:eb:
+                    1c:cb:28:21:5f:b6:1c:d8:e7:a2:04:22:f9:d3:da:
+                    d8:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/server1.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/CPS
+
+            X509v3 Subject Key Identifier: 
+                68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+            1.2.840.113533.7.65.0: 
+                0
+..V7.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         0c:b0:84:7c:2d:13:fe:9a:3d:bf:18:05:95:3d:20:48:a3:16:
+         81:87:15:50:15:a4:88:8d:9f:60:d4:3a:6f:eb:2d:6e:3a:86:
+         a4:a9:d2:c1:9d:89:7a:08:1c:a4:2d:b3:47:8e:0f:64:4a:6f:
+         66:03:83:3f:4f:34:94:36:aa:29:6d:8b:8d:02:22:2b:8c:cd:
+         77:a5:70:95:86:91:d1:b6:bf:52:be:33:6a:6b:99:f9:6f:e1:
+         12:be:04:cb:33:bf:f5:12:1a:4e:44:ba:5b:16:4d:30:b9:f3:
+         b4:74:ce:6e:f2:68:56:58:dd:d8:a1:fd:54:05:f4:23:91:85:
+         c9:f9
+-----BEGIN CERTIFICATE-----
+MIIEmzCCBASgAwIBAgIEQoctTDANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNzAx
+MDUxOTIwMzlaFw0xNzAxMDUxOTUwMzlaMIGwMQswCQYDVQQGEwJVUzEWMBQGA1UE
+ChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBp
+cyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBF
+bnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2lbZD
+QvrGbSpvSN+UTDlXBe7DeRFBaDbt7P6aAY+hOCj89xBGZi5NHhqxGk7G0cCViLDJ
+/zGLMwPbt4N7PiCEXu2yViin+OC5QHE3xctHDpcqaMAilWIV20fZ9dAr/4JLya0+
+3kzbkIBQPwmKhADsMAo9GM37/SpZmiOVFyxFnh9uQ3ltDFyY/kinxSNHXF79buce
+tPZoRdGGg1uiio2x4ymA/iVxiK2+vI+sUpZLqlGN5BMxGehOTZ/brLNq1bw5VHHK
+enp/kN19HYDZgbtZJsIR/uaT4veA5GX7NDcOKYBwTa84hi6ef1evnheu6xzLKCFf
+thzY56IEIvnT2tjLAgMBAAGjggEnMIIBIzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9v
+Y3NwLmVudHJ1c3QubmV0MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50
+cnVzdC5uZXQvc2VydmVyMS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYB
+BQUHAgEWGmh0dHA6Ly93d3cuZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRokORn
+pKZTgMeGZqTx90tD+4S9bTAfBgNVHSMEGDAWgBTwF2ITVT2z/woAa/tQhJfz7WLQ
+GjAZBgkqhkiG9n0HQQAEDDAKGwRWNy4xAwIAgTANBgkqhkiG9w0BAQUFAAOBgQAM
+sIR8LRP+mj2/GAWVPSBIoxaBhxVQFaSIjZ9g1Dpv6y1uOoakqdLBnYl6CBykLbNH
+jg9kSm9mA4M/TzSUNqopbYuNAiIrjM13pXCVhpHRtr9SvjNqa5n5b+ESvgTLM7/1
+EhpORLpbFk0wufO0dM5u8mhWWN3Yof1UBfQjkYXJ+Q==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert44[] = {
+  0x30, 0x82, 0x04, 0x9b, 0x30, 0x82, 0x04, 0x04, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x42, 0x87, 0x2d, 0x4c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+  0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+  0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+  0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+  0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x31,
+  0x30, 0x35, 0x31, 0x39, 0x32, 0x30, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x31,
+  0x37, 0x30, 0x31, 0x30, 0x35, 0x31, 0x39, 0x35, 0x30, 0x33, 0x39, 0x5a,
+  0x30, 0x81, 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69,
+  0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,
+  0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45,
+  0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45,
+  0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x95, 0xb6, 0x43,
+  0x42, 0xfa, 0xc6, 0x6d, 0x2a, 0x6f, 0x48, 0xdf, 0x94, 0x4c, 0x39, 0x57,
+  0x05, 0xee, 0xc3, 0x79, 0x11, 0x41, 0x68, 0x36, 0xed, 0xec, 0xfe, 0x9a,
+  0x01, 0x8f, 0xa1, 0x38, 0x28, 0xfc, 0xf7, 0x10, 0x46, 0x66, 0x2e, 0x4d,
+  0x1e, 0x1a, 0xb1, 0x1a, 0x4e, 0xc6, 0xd1, 0xc0, 0x95, 0x88, 0xb0, 0xc9,
+  0xff, 0x31, 0x8b, 0x33, 0x03, 0xdb, 0xb7, 0x83, 0x7b, 0x3e, 0x20, 0x84,
+  0x5e, 0xed, 0xb2, 0x56, 0x28, 0xa7, 0xf8, 0xe0, 0xb9, 0x40, 0x71, 0x37,
+  0xc5, 0xcb, 0x47, 0x0e, 0x97, 0x2a, 0x68, 0xc0, 0x22, 0x95, 0x62, 0x15,
+  0xdb, 0x47, 0xd9, 0xf5, 0xd0, 0x2b, 0xff, 0x82, 0x4b, 0xc9, 0xad, 0x3e,
+  0xde, 0x4c, 0xdb, 0x90, 0x80, 0x50, 0x3f, 0x09, 0x8a, 0x84, 0x00, 0xec,
+  0x30, 0x0a, 0x3d, 0x18, 0xcd, 0xfb, 0xfd, 0x2a, 0x59, 0x9a, 0x23, 0x95,
+  0x17, 0x2c, 0x45, 0x9e, 0x1f, 0x6e, 0x43, 0x79, 0x6d, 0x0c, 0x5c, 0x98,
+  0xfe, 0x48, 0xa7, 0xc5, 0x23, 0x47, 0x5c, 0x5e, 0xfd, 0x6e, 0xe7, 0x1e,
+  0xb4, 0xf6, 0x68, 0x45, 0xd1, 0x86, 0x83, 0x5b, 0xa2, 0x8a, 0x8d, 0xb1,
+  0xe3, 0x29, 0x80, 0xfe, 0x25, 0x71, 0x88, 0xad, 0xbe, 0xbc, 0x8f, 0xac,
+  0x52, 0x96, 0x4b, 0xaa, 0x51, 0x8d, 0xe4, 0x13, 0x31, 0x19, 0xe8, 0x4e,
+  0x4d, 0x9f, 0xdb, 0xac, 0xb3, 0x6a, 0xd5, 0xbc, 0x39, 0x54, 0x71, 0xca,
+  0x7a, 0x7a, 0x7f, 0x90, 0xdd, 0x7d, 0x1d, 0x80, 0xd9, 0x81, 0xbb, 0x59,
+  0x26, 0xc2, 0x11, 0xfe, 0xe6, 0x93, 0xe2, 0xf7, 0x80, 0xe4, 0x65, 0xfb,
+  0x34, 0x37, 0x0e, 0x29, 0x80, 0x70, 0x4d, 0xaf, 0x38, 0x86, 0x2e, 0x9e,
+  0x7f, 0x57, 0xaf, 0x9e, 0x17, 0xae, 0xeb, 0x1c, 0xcb, 0x28, 0x21, 0x5f,
+  0xb6, 0x1c, 0xd8, 0xe7, 0xa2, 0x04, 0x22, 0xf9, 0xd3, 0xda, 0xd8, 0xcb,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x27, 0x30, 0x82, 0x01,
+  0x23, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x33,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27,
+  0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f,
+  0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+  0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72,
+  0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x68, 0x90, 0xe4, 0x67,
+  0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86, 0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43,
+  0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d, 0xb3,
+  0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97, 0xf3, 0xed, 0x62, 0xd0,
+  0x1a, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07,
+  0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31,
+  0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x0c,
+  0xb0, 0x84, 0x7c, 0x2d, 0x13, 0xfe, 0x9a, 0x3d, 0xbf, 0x18, 0x05, 0x95,
+  0x3d, 0x20, 0x48, 0xa3, 0x16, 0x81, 0x87, 0x15, 0x50, 0x15, 0xa4, 0x88,
+  0x8d, 0x9f, 0x60, 0xd4, 0x3a, 0x6f, 0xeb, 0x2d, 0x6e, 0x3a, 0x86, 0xa4,
+  0xa9, 0xd2, 0xc1, 0x9d, 0x89, 0x7a, 0x08, 0x1c, 0xa4, 0x2d, 0xb3, 0x47,
+  0x8e, 0x0f, 0x64, 0x4a, 0x6f, 0x66, 0x03, 0x83, 0x3f, 0x4f, 0x34, 0x94,
+  0x36, 0xaa, 0x29, 0x6d, 0x8b, 0x8d, 0x02, 0x22, 0x2b, 0x8c, 0xcd, 0x77,
+  0xa5, 0x70, 0x95, 0x86, 0x91, 0xd1, 0xb6, 0xbf, 0x52, 0xbe, 0x33, 0x6a,
+  0x6b, 0x99, 0xf9, 0x6f, 0xe1, 0x12, 0xbe, 0x04, 0xcb, 0x33, 0xbf, 0xf5,
+  0x12, 0x1a, 0x4e, 0x44, 0xba, 0x5b, 0x16, 0x4d, 0x30, 0xb9, 0xf3, 0xb4,
+  0x74, 0xce, 0x6e, 0xf2, 0x68, 0x56, 0x58, 0xdd, 0xd8, 0xa1, 0xfd, 0x54,
+  0x05, 0xf4, 0x23, 0x91, 0x85, 0xc9, 0xf9,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            69:48:a2:6b:20:1a:a4:21:e8:98:b1:c4:92:c7:c5:8e
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+        Validity
+            Not Before: Nov 29 00:00:00 2006 GMT
+            Not After : Nov 28 23:59:59 2016 GMT
+        Subject: C=US, O=GeoTrust Inc, OU=See www.geotrust.com/resources/cps (c)06, CN=GeoTrust Extended Validation SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c2:ef:ed:ec:0b:2d:72:8a:74:68:73:36:6e:10:
+                    a8:7e:48:7f:58:bb:78:67:dc:ed:7b:d6:7c:a6:4f:
+                    3d:9f:5d:6f:0a:d0:a0:b4:65:fd:be:d3:bf:77:b6:
+                    94:a5:82:ff:81:95:9d:28:10:06:ec:c2:b4:90:aa:
+                    5a:51:4c:73:d9:6b:74:a8:35:49:f4:a6:36:80:d4:
+                    5c:75:9e:9e:7c:01:c7:8c:9c:81:c8:86:83:1a:8e:
+                    bd:00:13:a2:dc:ff:a5:78:aa:77:2c:21:62:08:97:
+                    3f:80:bd:f7:67:a4:79:db:7d:d7:3e:6e:b6:d5:96:
+                    b9:98:86:4e:7a:67:e2:93:af:da:a5:d1:27:fb:f1:
+                    66:c3:2a:03:0c:b6:c7:82:1d:39:fb:3c:de:29:36:
+                    71:5d:e1:a8:b5:16:39:7c:1b:ff:7b:86:f5:80:92:
+                    95:e0:03:3b:aa:44:fb:f4:00:b5:e5:a9:e2:fa:18:
+                    f9:84:9a:c1:e1:f6:2e:0e:81:8b:14:29:34:ff:1f:
+                    55:60:88:a4:99:c6:6f:6f:04:39:3a:75:a4:a7:1e:
+                    58:df:b7:ff:c9:9a:1d:70:db:83:a0:d3:83:1b:2d:
+                    6d:2a:90:5b:a3:63:91:73:b5:ff:9d:82:7a:41:f3:
+                    d3:aa:2f:0b:0d:9f:cf:44:c0:5e:c7:a1:6b:cf:ae:
+                    94:db
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                28:C4:EB:8F:F1:5F:79:90:A3:2B:55:C3:56:4E:7D:6B:53:72:2C:18
+            Authority Information Access: 
+                OCSP - URI:http://EVSecure-ocsp.geotrust.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.geotrust.com/resources/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://EVSecure-crl.geotrust.com/GeoTrustPCA.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+
+    Signature Algorithm: sha1WithRSAEncryption
+         02:60:a3:16:12:9d:d8:1c:19:e4:5a:37:6c:ff:32:98:37:46:
+         4f:bc:81:7c:80:c3:ca:89:2a:00:fe:5e:3e:ec:ba:8c:2b:1f:
+         ab:95:6b:91:94:21:a0:60:1f:02:06:fa:cf:17:6d:f8:95:ab:
+         cd:78:23:14:96:c0:9d:1f:1b:eb:50:e1:65:42:8a:d2:b3:c9:
+         ad:80:c3:67:cf:b4:58:1b:d5:04:e4:58:fe:34:45:e0:fb:a4:
+         84:22:8b:e9:e2:37:4c:98:f1:0b:ff:a4:89:53:d1:4d:c0:68:
+         48:d7:59:87:1a:3b:7d:f5:d0:f9:23:72:ca:60:fd:c3:22:15:
+         f0:9a:95:58:6f:7c:24:93:ec:a5:12:3d:b4:1b:01:e8:ee:69:
+         ed:41:6b:52:cb:9a:b7:5c:15:d1:bd:06:40:7a:e0:0c:97:cb:
+         60:e7:82:5f:6a:5f:de:49:84:56:6a:af:7c:b0:4b:ad:8c:4f:
+         0f:79:a0:cc:11:3c:25:e7:46:bf:7a:d0:2f:88:c8:bf:eb:94:
+         0b:6a:75:33:7f:73:00:b8:12:70:23:5e:55:7f:45:5b:1e:10:
+         b1:02:68:d8:27:40:cf:24:09:e2:65:74:ce:89:44:8d:7b:28:
+         90:68:ae:ac:c2:38:c8:56:0d:33:88:28:7f:54:fc:3c:3c:50:
+         09:93:3d:38
+-----BEGIN CERTIFICATE-----
+MIIEnDCCA4SgAwIBAgIQaUiiayAapCHomLHEksfFjjANBgkqhkiG9w0BAQUFADBY
+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
+MjkwMDAwMDBaFw0xNjExMjgyMzU5NTlaMIGFMQswCQYDVQQGEwJVUzEVMBMGA1UE
+ChMMR2VvVHJ1c3QgSW5jMTEwLwYDVQQLEyhTZWUgd3d3Lmdlb3RydXN0LmNvbS9y
+ZXNvdXJjZXMvY3BzIChjKTA2MSwwKgYDVQQDEyNHZW9UcnVzdCBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMLv7ewLLXKKdGhzNm4QqH5If1i7eGfc7XvWfKZPPZ9dbwrQoLRl/b7Tv3e2lKWC
+/4GVnSgQBuzCtJCqWlFMc9lrdKg1SfSmNoDUXHWennwBx4ycgciGgxqOvQATotz/
+pXiqdywhYgiXP4C992ekedt91z5uttWWuZiGTnpn4pOv2qXRJ/vxZsMqAwy2x4Id
+Ofs83ik2cV3hqLUWOXwb/3uG9YCSleADO6pE+/QAteWp4voY+YSaweH2Lg6BixQp
+NP8fVWCIpJnGb28EOTp1pKceWN+3/8maHXDbg6DTgxstbSqQW6NjkXO1/52CekHz
+06ovCw2fz0TAXseha8+ulNsCAwEAAaOCATIwggEuMB0GA1UdDgQWBBQoxOuP8V95
+kKMrVcNWTn1rU3IsGDA9BggrBgEFBQcBAQQxMC8wLQYIKwYBBQUHMAGGIWh0dHA6
+Ly9FVlNlY3VyZS1vY3NwLmdlb3RydXN0LmNvbTASBgNVHRMBAf8ECDAGAQH/AgEA
+MEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdl
+b3RydXN0LmNvbS9yZXNvdXJjZXMvY3BzMEEGA1UdHwQ6MDgwNqA0oDKGMGh0dHA6
+Ly9FVlNlY3VyZS1jcmwuZ2VvdHJ1c3QuY29tL0dlb1RydXN0UENBLmNybDAOBgNV
+HQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
+KoZIhvcNAQEFBQADggEBAAJgoxYSndgcGeRaN2z/Mpg3Rk+8gXyAw8qJKgD+Xj7s
+uowrH6uVa5GUIaBgHwIG+s8XbfiVq814IxSWwJ0fG+tQ4WVCitKzya2Aw2fPtFgb
+1QTkWP40ReD7pIQii+niN0yY8Qv/pIlT0U3AaEjXWYcaO3310Pkjcspg/cMiFfCa
+lVhvfCST7KUSPbQbAejuae1Ba1LLmrdcFdG9BkB64AyXy2Dngl9qX95JhFZqr3yw
+S62MTw95oMwRPCXnRr960C+IyL/rlAtqdTN/cwC4EnAjXlV/RVseELECaNgnQM8k
+CeJldM6JRI17KJBorqzCOMhWDTOIKH9U/Dw8UAmTPTg=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert45[] = {
+  0x30, 0x82, 0x04, 0x9c, 0x30, 0x82, 0x03, 0x84, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x69, 0x48, 0xa2, 0x6b, 0x20, 0x1a, 0xa4, 0x21, 0xe8,
+  0x98, 0xb1, 0xc4, 0x92, 0xc7, 0xc5, 0x8e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x58,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+  0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28,
+  0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69,
+  0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31,
+  0x32, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31,
+  0x36, 0x31, 0x31, 0x32, 0x38, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a,
+  0x30, 0x81, 0x85, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0c, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x49, 0x6e, 0x63, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x28, 0x53, 0x65, 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65,
+  0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73,
+  0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x2c, 0x30, 0x2a, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x23, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+  0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xc2, 0xef, 0xed, 0xec, 0x0b, 0x2d, 0x72, 0x8a, 0x74, 0x68, 0x73,
+  0x36, 0x6e, 0x10, 0xa8, 0x7e, 0x48, 0x7f, 0x58, 0xbb, 0x78, 0x67, 0xdc,
+  0xed, 0x7b, 0xd6, 0x7c, 0xa6, 0x4f, 0x3d, 0x9f, 0x5d, 0x6f, 0x0a, 0xd0,
+  0xa0, 0xb4, 0x65, 0xfd, 0xbe, 0xd3, 0xbf, 0x77, 0xb6, 0x94, 0xa5, 0x82,
+  0xff, 0x81, 0x95, 0x9d, 0x28, 0x10, 0x06, 0xec, 0xc2, 0xb4, 0x90, 0xaa,
+  0x5a, 0x51, 0x4c, 0x73, 0xd9, 0x6b, 0x74, 0xa8, 0x35, 0x49, 0xf4, 0xa6,
+  0x36, 0x80, 0xd4, 0x5c, 0x75, 0x9e, 0x9e, 0x7c, 0x01, 0xc7, 0x8c, 0x9c,
+  0x81, 0xc8, 0x86, 0x83, 0x1a, 0x8e, 0xbd, 0x00, 0x13, 0xa2, 0xdc, 0xff,
+  0xa5, 0x78, 0xaa, 0x77, 0x2c, 0x21, 0x62, 0x08, 0x97, 0x3f, 0x80, 0xbd,
+  0xf7, 0x67, 0xa4, 0x79, 0xdb, 0x7d, 0xd7, 0x3e, 0x6e, 0xb6, 0xd5, 0x96,
+  0xb9, 0x98, 0x86, 0x4e, 0x7a, 0x67, 0xe2, 0x93, 0xaf, 0xda, 0xa5, 0xd1,
+  0x27, 0xfb, 0xf1, 0x66, 0xc3, 0x2a, 0x03, 0x0c, 0xb6, 0xc7, 0x82, 0x1d,
+  0x39, 0xfb, 0x3c, 0xde, 0x29, 0x36, 0x71, 0x5d, 0xe1, 0xa8, 0xb5, 0x16,
+  0x39, 0x7c, 0x1b, 0xff, 0x7b, 0x86, 0xf5, 0x80, 0x92, 0x95, 0xe0, 0x03,
+  0x3b, 0xaa, 0x44, 0xfb, 0xf4, 0x00, 0xb5, 0xe5, 0xa9, 0xe2, 0xfa, 0x18,
+  0xf9, 0x84, 0x9a, 0xc1, 0xe1, 0xf6, 0x2e, 0x0e, 0x81, 0x8b, 0x14, 0x29,
+  0x34, 0xff, 0x1f, 0x55, 0x60, 0x88, 0xa4, 0x99, 0xc6, 0x6f, 0x6f, 0x04,
+  0x39, 0x3a, 0x75, 0xa4, 0xa7, 0x1e, 0x58, 0xdf, 0xb7, 0xff, 0xc9, 0x9a,
+  0x1d, 0x70, 0xdb, 0x83, 0xa0, 0xd3, 0x83, 0x1b, 0x2d, 0x6d, 0x2a, 0x90,
+  0x5b, 0xa3, 0x63, 0x91, 0x73, 0xb5, 0xff, 0x9d, 0x82, 0x7a, 0x41, 0xf3,
+  0xd3, 0xaa, 0x2f, 0x0b, 0x0d, 0x9f, 0xcf, 0x44, 0xc0, 0x5e, 0xc7, 0xa1,
+  0x6b, 0xcf, 0xae, 0x94, 0xdb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0x32, 0x30, 0x82, 0x01, 0x2e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x28, 0xc4, 0xeb, 0x8f, 0xf1, 0x5f, 0x79,
+  0x90, 0xa3, 0x2b, 0x55, 0xc3, 0x56, 0x4e, 0x7d, 0x6b, 0x53, 0x72, 0x2c,
+  0x18, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+  0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f,
+  0x63, 0x73, 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+  0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30,
+  0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65,
+  0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73,
+  0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3a, 0x30, 0x38, 0x30,
+  0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x63,
+  0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0x2c, 0xd5, 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b,
+  0x4a, 0xfb, 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x02, 0x60, 0xa3, 0x16, 0x12, 0x9d, 0xd8, 0x1c,
+  0x19, 0xe4, 0x5a, 0x37, 0x6c, 0xff, 0x32, 0x98, 0x37, 0x46, 0x4f, 0xbc,
+  0x81, 0x7c, 0x80, 0xc3, 0xca, 0x89, 0x2a, 0x00, 0xfe, 0x5e, 0x3e, 0xec,
+  0xba, 0x8c, 0x2b, 0x1f, 0xab, 0x95, 0x6b, 0x91, 0x94, 0x21, 0xa0, 0x60,
+  0x1f, 0x02, 0x06, 0xfa, 0xcf, 0x17, 0x6d, 0xf8, 0x95, 0xab, 0xcd, 0x78,
+  0x23, 0x14, 0x96, 0xc0, 0x9d, 0x1f, 0x1b, 0xeb, 0x50, 0xe1, 0x65, 0x42,
+  0x8a, 0xd2, 0xb3, 0xc9, 0xad, 0x80, 0xc3, 0x67, 0xcf, 0xb4, 0x58, 0x1b,
+  0xd5, 0x04, 0xe4, 0x58, 0xfe, 0x34, 0x45, 0xe0, 0xfb, 0xa4, 0x84, 0x22,
+  0x8b, 0xe9, 0xe2, 0x37, 0x4c, 0x98, 0xf1, 0x0b, 0xff, 0xa4, 0x89, 0x53,
+  0xd1, 0x4d, 0xc0, 0x68, 0x48, 0xd7, 0x59, 0x87, 0x1a, 0x3b, 0x7d, 0xf5,
+  0xd0, 0xf9, 0x23, 0x72, 0xca, 0x60, 0xfd, 0xc3, 0x22, 0x15, 0xf0, 0x9a,
+  0x95, 0x58, 0x6f, 0x7c, 0x24, 0x93, 0xec, 0xa5, 0x12, 0x3d, 0xb4, 0x1b,
+  0x01, 0xe8, 0xee, 0x69, 0xed, 0x41, 0x6b, 0x52, 0xcb, 0x9a, 0xb7, 0x5c,
+  0x15, 0xd1, 0xbd, 0x06, 0x40, 0x7a, 0xe0, 0x0c, 0x97, 0xcb, 0x60, 0xe7,
+  0x82, 0x5f, 0x6a, 0x5f, 0xde, 0x49, 0x84, 0x56, 0x6a, 0xaf, 0x7c, 0xb0,
+  0x4b, 0xad, 0x8c, 0x4f, 0x0f, 0x79, 0xa0, 0xcc, 0x11, 0x3c, 0x25, 0xe7,
+  0x46, 0xbf, 0x7a, 0xd0, 0x2f, 0x88, 0xc8, 0xbf, 0xeb, 0x94, 0x0b, 0x6a,
+  0x75, 0x33, 0x7f, 0x73, 0x00, 0xb8, 0x12, 0x70, 0x23, 0x5e, 0x55, 0x7f,
+  0x45, 0x5b, 0x1e, 0x10, 0xb1, 0x02, 0x68, 0xd8, 0x27, 0x40, 0xcf, 0x24,
+  0x09, 0xe2, 0x65, 0x74, 0xce, 0x89, 0x44, 0x8d, 0x7b, 0x28, 0x90, 0x68,
+  0xae, 0xac, 0xc2, 0x38, 0xc8, 0x56, 0x0d, 0x33, 0x88, 0x28, 0x7f, 0x54,
+  0xfc, 0x3c, 0x3c, 0x50, 0x09, 0x93, 0x3d, 0x38,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1184796954 (0x469e911a)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+        Validity
+            Not Before: Mar 23 15:18:27 2009 GMT
+            Not After : Mar 23 15:48:27 2019 GMT
+        Subject: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ad:4d:4b:a9:12:86:b2:ea:a3:20:07:15:16:64:
+                    2a:2b:4b:d1:bf:0b:4a:4d:8e:ed:80:76:a5:67:b7:
+                    78:40:c0:73:42:c8:68:c0:db:53:2b:dd:5e:b8:76:
+                    98:35:93:8b:1a:9d:7c:13:3a:0e:1f:5b:b7:1e:cf:
+                    e5:24:14:1e:b1:81:a9:8d:7d:b8:cc:6b:4b:03:f1:
+                    02:0c:dc:ab:a5:40:24:00:7f:74:94:a1:9d:08:29:
+                    b3:88:0b:f5:87:77:9d:55:cd:e4:c3:7e:d7:6a:64:
+                    ab:85:14:86:95:5b:97:32:50:6f:3d:c8:ba:66:0c:
+                    e3:fc:bd:b8:49:c1:76:89:49:19:fd:c0:a8:bd:89:
+                    a3:67:2f:c6:9f:bc:71:19:60:b8:2d:e9:2c:c9:90:
+                    76:66:7b:94:e2:af:78:d6:65:53:5d:3c:d6:9c:b2:
+                    cf:29:03:f9:2f:a4:50:b2:d4:48:ce:05:32:55:8a:
+                    fd:b2:64:4c:0e:e4:98:07:75:db:7f:df:b9:08:55:
+                    60:85:30:29:f9:7b:48:a4:69:86:e3:35:3f:1e:86:
+                    5d:7a:7a:15:bd:ef:00:8e:15:22:54:17:00:90:26:
+                    93:bc:0e:49:68:91:bf:f8:47:d3:9d:95:42:c1:0e:
+                    4d:df:6f:26:cf:c3:18:21:62:66:43:70:d6:d5:c0:
+                    07:e1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/server1.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/CPS
+
+            X509v3 Subject Key Identifier: 
+                55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+            X509v3 Authority Key Identifier: 
+                keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+            1.2.840.113533.7.65.0: 
+                0
+..V7.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         8f:65:a2:30:8e:26:ab:8a:ec:35:16:98:e9:03:f0:8d:17:5f:
+         bc:4c:6c:02:f6:74:52:e0:c2:c6:1f:ce:f2:a6:11:0c:a8:b1:
+         0e:4d:84:8b:71:36:ef:b3:35:45:f3:c1:f8:96:c5:8b:55:a4:
+         cc:6b:83:16:20:32:da:be:fb:af:9b:b7:9f:e1:7e:84:9f:9e:
+         3c:50:a7:3f:5c:c2:be:8b:86:b8:08:92:ee:f8:42:2b:0d:13:
+         e3:76:85:48:0a:4a:bf:d0:a5:3b:0a:b0:54:b8:6d:e3:08:f9:
+         34:8d:0b:8e:8b:12:cc:17:1a:33:87:95:c8:9e:0a:dc:50:53:
+         17:7b
+-----BEGIN CERTIFICATE-----
+MIIEnzCCBAigAwIBAgIERp6RGjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wOTAz
+MjMxNTE4MjdaFw0xOTAzMjMxNTQ4MjdaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5l
+dDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5u
+ZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+rU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3ed
+Vc3kw37XamSrhRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4
+LeksyZB2ZnuU4q941mVTXTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5
+CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N
+328mz8MYIWJmQ3DW1cAH4QIDAQABo4IBJzCCASMwDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRw
+Oi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js
+LmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYG
+CCsGAQUFBwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NQUzAdBgNVHQ4EFgQU
+VeSB0RGAvtiJuQijMfmhJAkWuXAwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX
+8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEFBQAD
+gYEAj2WiMI4mq4rsNRaY6QPwjRdfvExsAvZ0UuDCxh/O8qYRDKixDk2Ei3E277M1
+RfPB+JbFi1WkzGuDFiAy2r77r5u3n+F+hJ+ePFCnP1zCvouGuAiS7vhCKw0T43aF
+SApKv9ClOwqwVLht4wj5NI0LjosSzBcaM4eVyJ4K3FBTF3s=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert46[] = {
+  0x30, 0x82, 0x04, 0x9f, 0x30, 0x82, 0x04, 0x08, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x46, 0x9e, 0x91, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+  0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+  0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+  0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+  0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x33,
+  0x32, 0x33, 0x31, 0x35, 0x31, 0x38, 0x32, 0x37, 0x5a, 0x17, 0x0d, 0x31,
+  0x39, 0x30, 0x33, 0x32, 0x33, 0x31, 0x35, 0x34, 0x38, 0x32, 0x37, 0x5a,
+  0x30, 0x81, 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+  0x74, 0x31, 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37,
+  0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38,
+  0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20,
+  0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73,
+  0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39,
+  0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33,
+  0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72,
+  0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41,
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30,
+  0x34, 0x38, 0x29, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xad, 0x4d, 0x4b, 0xa9, 0x12, 0x86, 0xb2, 0xea, 0xa3, 0x20, 0x07, 0x15,
+  0x16, 0x64, 0x2a, 0x2b, 0x4b, 0xd1, 0xbf, 0x0b, 0x4a, 0x4d, 0x8e, 0xed,
+  0x80, 0x76, 0xa5, 0x67, 0xb7, 0x78, 0x40, 0xc0, 0x73, 0x42, 0xc8, 0x68,
+  0xc0, 0xdb, 0x53, 0x2b, 0xdd, 0x5e, 0xb8, 0x76, 0x98, 0x35, 0x93, 0x8b,
+  0x1a, 0x9d, 0x7c, 0x13, 0x3a, 0x0e, 0x1f, 0x5b, 0xb7, 0x1e, 0xcf, 0xe5,
+  0x24, 0x14, 0x1e, 0xb1, 0x81, 0xa9, 0x8d, 0x7d, 0xb8, 0xcc, 0x6b, 0x4b,
+  0x03, 0xf1, 0x02, 0x0c, 0xdc, 0xab, 0xa5, 0x40, 0x24, 0x00, 0x7f, 0x74,
+  0x94, 0xa1, 0x9d, 0x08, 0x29, 0xb3, 0x88, 0x0b, 0xf5, 0x87, 0x77, 0x9d,
+  0x55, 0xcd, 0xe4, 0xc3, 0x7e, 0xd7, 0x6a, 0x64, 0xab, 0x85, 0x14, 0x86,
+  0x95, 0x5b, 0x97, 0x32, 0x50, 0x6f, 0x3d, 0xc8, 0xba, 0x66, 0x0c, 0xe3,
+  0xfc, 0xbd, 0xb8, 0x49, 0xc1, 0x76, 0x89, 0x49, 0x19, 0xfd, 0xc0, 0xa8,
+  0xbd, 0x89, 0xa3, 0x67, 0x2f, 0xc6, 0x9f, 0xbc, 0x71, 0x19, 0x60, 0xb8,
+  0x2d, 0xe9, 0x2c, 0xc9, 0x90, 0x76, 0x66, 0x7b, 0x94, 0xe2, 0xaf, 0x78,
+  0xd6, 0x65, 0x53, 0x5d, 0x3c, 0xd6, 0x9c, 0xb2, 0xcf, 0x29, 0x03, 0xf9,
+  0x2f, 0xa4, 0x50, 0xb2, 0xd4, 0x48, 0xce, 0x05, 0x32, 0x55, 0x8a, 0xfd,
+  0xb2, 0x64, 0x4c, 0x0e, 0xe4, 0x98, 0x07, 0x75, 0xdb, 0x7f, 0xdf, 0xb9,
+  0x08, 0x55, 0x60, 0x85, 0x30, 0x29, 0xf9, 0x7b, 0x48, 0xa4, 0x69, 0x86,
+  0xe3, 0x35, 0x3f, 0x1e, 0x86, 0x5d, 0x7a, 0x7a, 0x15, 0xbd, 0xef, 0x00,
+  0x8e, 0x15, 0x22, 0x54, 0x17, 0x00, 0x90, 0x26, 0x93, 0xbc, 0x0e, 0x49,
+  0x68, 0x91, 0xbf, 0xf8, 0x47, 0xd3, 0x9d, 0x95, 0x42, 0xc1, 0x0e, 0x4d,
+  0xdf, 0x6f, 0x26, 0xcf, 0xc3, 0x18, 0x21, 0x62, 0x66, 0x43, 0x70, 0xd6,
+  0xd5, 0xc0, 0x07, 0xe1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+  0x27, 0x30, 0x82, 0x01, 0x23, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+  0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24,
+  0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+  0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30,
+  0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50,
+  0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9, 0x08, 0xa3,
+  0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9, 0x70, 0x30, 0x1f, 0x06, 0x03,
+  0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62,
+  0x13, 0x55, 0x3d, 0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97,
+  0xf3, 0xed, 0x62, 0xd0, 0x1a, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04,
+  0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x81, 0x81, 0x00, 0x8f, 0x65, 0xa2, 0x30, 0x8e, 0x26, 0xab, 0x8a, 0xec,
+  0x35, 0x16, 0x98, 0xe9, 0x03, 0xf0, 0x8d, 0x17, 0x5f, 0xbc, 0x4c, 0x6c,
+  0x02, 0xf6, 0x74, 0x52, 0xe0, 0xc2, 0xc6, 0x1f, 0xce, 0xf2, 0xa6, 0x11,
+  0x0c, 0xa8, 0xb1, 0x0e, 0x4d, 0x84, 0x8b, 0x71, 0x36, 0xef, 0xb3, 0x35,
+  0x45, 0xf3, 0xc1, 0xf8, 0x96, 0xc5, 0x8b, 0x55, 0xa4, 0xcc, 0x6b, 0x83,
+  0x16, 0x20, 0x32, 0xda, 0xbe, 0xfb, 0xaf, 0x9b, 0xb7, 0x9f, 0xe1, 0x7e,
+  0x84, 0x9f, 0x9e, 0x3c, 0x50, 0xa7, 0x3f, 0x5c, 0xc2, 0xbe, 0x8b, 0x86,
+  0xb8, 0x08, 0x92, 0xee, 0xf8, 0x42, 0x2b, 0x0d, 0x13, 0xe3, 0x76, 0x85,
+  0x48, 0x0a, 0x4a, 0xbf, 0xd0, 0xa5, 0x3b, 0x0a, 0xb0, 0x54, 0xb8, 0x6d,
+  0xe3, 0x08, 0xf9, 0x34, 0x8d, 0x0b, 0x8e, 0x8b, 0x12, 0xcc, 0x17, 0x1a,
+  0x33, 0x87, 0x95, 0xc8, 0x9e, 0x0a, 0xdc, 0x50, 0x53, 0x17, 0x7b,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            5a:b6:1d:ac:1e:4d:a2:06:14:c7:55:3d:3d:a9:b2:dc
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Validity
+            Not Before: Oct 23 00:00:00 2008 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=FR, O=GANDI SAS, CN=Gandi Standard SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:54:3d:a5:db:0d:22:78:50:6a:5a:23:89:3f:
+                    97:a1:d4:07:1a:a9:58:08:9b:a0:15:c3:32:b6:b7:
+                    f1:e8:b9:a5:6f:ad:37:f6:6e:71:1b:b4:75:2d:48:
+                    5e:9f:c6:15:aa:81:ef:e5:c4:88:95:8a:3a:6c:77:
+                    cc:b5:cd:65:e4:67:e5:73:c9:50:52:94:c1:27:49:
+                    3e:a0:6b:41:16:41:b6:94:99:41:ae:3e:cb:e2:06:
+                    46:09:e9:4d:be:c9:4c:55:a9:18:7e:a6:df:6e:fd:
+                    4a:b2:cc:6c:4e:d9:c8:50:15:93:b3:f2:e9:e3:c2:
+                    6a:ad:3a:d5:fb:c3:79:50:9f:25:79:29:b2:47:64:
+                    7c:20:3e:e2:08:4d:93:29:14:b6:34:6e:cf:71:46:
+                    7e:76:10:f4:fd:6c:aa:01:d2:c2:06:de:92:83:cc:
+                    58:90:2e:92:de:1e:65:b7:63:2f:3d:b2:eb:70:8c:
+                    4c:e0:be:15:9d:de:c1:4d:56:f8:0b:c6:8e:07:b9:
+                    5d:df:95:f0:7b:40:1f:1a:2c:d7:9c:2b:4b:76:f4:
+                    59:f5:43:c1:2c:66:10:9e:9e:66:96:60:9d:1c:74:
+                    1b:4e:18:5c:08:b0:6e:6c:ca:69:1a:02:e9:bb:ca:
+                    78:ef:66:2e:e3:32:fd:41:5c:95:74:81:4d:f4:da:
+                    fe:4b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+            X509v3 Subject Key Identifier: 
+                B6:A8:FF:A2:A8:2F:D0:A6:CD:4B:B1:68:F3:E7:50:10:31:A7:79:21
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.2.26
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/UTNAddTrustServer_CA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         19:53:bf:03:3d:9b:e2:6b:5a:fd:ba:49:1f:4f:ec:e1:c6:82:
+         39:3c:d2:03:04:0f:ab:7b:3e:82:a9:85:10:1f:f4:de:32:af:
+         58:3f:ff:70:f3:30:1d:97:2d:4c:9a:e2:ec:0c:3e:14:2d:2f:
+         98:48:9d:ae:16:6a:ac:2d:42:aa:b5:64:a4:70:bb:eb:73:94:
+         7b:46:4c:e7:7a:14:76:5b:4c:1d:84:a1:20:74:1f:2e:4b:5c:
+         70:88:dc:bd:f7:19:3d:ed:59:0d:e2:3f:26:e2:9c:ac:a4:3c:
+         95:1c:f8:be:8c:03:ae:f0:e5:9c:4d:bc:c7:9b:58:00:bf:af:
+         ad:fa:37:6e:71:6d:18:34:0e:c1:ea:6a:f8:0d:df:69:54:56:
+         15:f2:28:b3:fe:a4:63:ec:c5:04:64:60:bb:fe:2a:f0:f4:87:
+         a1:b0:ae:bd:aa:e4:2f:e3:03:0b:2f:66:5f:85:a4:32:7b:46:
+         ed:25:0c:e7:f1:b7:e7:19:fd:60:ba:5f:87:77:de:98:07:96:
+         e4:5e:ea:63:7d:a8:de:55:da:61:5c:3c:90:83:43:04:07:3c:
+         dd:f3:f8:9f:06:52:0a:de:c7:b6:7b:8f:e1:11:f7:04:7a:35:
+         ff:6a:bc:5b:c7:50:49:08:70:6f:94:43:cd:9e:c7:70:f1:db:
+         d0:6d:da:8f
+-----BEGIN CERTIFICATE-----
+MIIEozCCA4ugAwIBAgIQWrYdrB5NogYUx1U9Pamy3DANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDgxMDIzMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBBMQswCQYD
+VQQGEwJGUjESMBAGA1UEChMJR0FOREkgU0FTMR4wHAYDVQQDExVHYW5kaSBTdGFu
+ZGFyZCBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2VD2l
+2w0ieFBqWiOJP5eh1AcaqVgIm6AVwzK2t/HouaVvrTf2bnEbtHUtSF6fxhWqge/l
+xIiVijpsd8y1zWXkZ+VzyVBSlMEnST6ga0EWQbaUmUGuPsviBkYJ6U2+yUxVqRh+
+pt9u/UqyzGxO2chQFZOz8unjwmqtOtX7w3lQnyV5KbJHZHwgPuIITZMpFLY0bs9x
+Rn52EPT9bKoB0sIG3pKDzFiQLpLeHmW3Yy89sutwjEzgvhWd3sFNVvgLxo4HuV3f
+lfB7QB8aLNecK0t29Fn1Q8EsZhCenmaWYJ0cdBtOGFwIsG5symkaAum7ynjvZi7j
+Mv1BXJV0gU302v5LAgMBAAGjggE+MIIBOjAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd
+BzfVhZadS9LDRTAdBgNVHQ4EFgQUtqj/oqgv0KbNS7Fo8+dQEDGneSEwDgYDVR0P
+AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwGAYDVR0gBBEwDzANBgsrBgEE
+AbIxAQICGjBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5j
+b20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5jcmwwdAYIKwYBBQUHAQEEaDBmMD0G
+CCsGAQUFBzAChjFodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RT
+ZXJ2ZXJfQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAZU78DPZvia1r9ukkfT+zhxoI5PNIDBA+r
+ez6CqYUQH/TeMq9YP/9w8zAdly1MmuLsDD4ULS+YSJ2uFmqsLUKqtWSkcLvrc5R7
+RkznehR2W0wdhKEgdB8uS1xwiNy99xk97VkN4j8m4pyspDyVHPi+jAOu8OWcTbzH
+m1gAv6+t+jducW0YNA7B6mr4Dd9pVFYV8iiz/qRj7MUEZGC7/irw9IehsK69quQv
+4wMLL2ZfhaQye0btJQzn8bfnGf1gul+Hd96YB5bkXupjfajeVdphXDyQg0MEBzzd
+8/ifBlIK3se2e4/hEfcEejX/arxbx1BJCHBvlEPNnsdw8dvQbdqP
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert47[] = {
+  0x30, 0x82, 0x04, 0xa3, 0x30, 0x82, 0x03, 0x8b, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x5a, 0xb6, 0x1d, 0xac, 0x1e, 0x4d, 0xa2, 0x06, 0x14,
+  0xc7, 0x55, 0x3d, 0x3d, 0xa9, 0xb2, 0xdc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+  0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+  0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x38, 0x31, 0x30, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+  0x38, 0x33, 0x38, 0x5a, 0x30, 0x41, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x46, 0x52, 0x31, 0x12, 0x30, 0x10, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x47, 0x41, 0x4e, 0x44, 0x49, 0x20,
+  0x53, 0x41, 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x15, 0x47, 0x61, 0x6e, 0x64, 0x69, 0x20, 0x53, 0x74, 0x61, 0x6e,
+  0x64, 0x61, 0x72, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x54, 0x3d, 0xa5,
+  0xdb, 0x0d, 0x22, 0x78, 0x50, 0x6a, 0x5a, 0x23, 0x89, 0x3f, 0x97, 0xa1,
+  0xd4, 0x07, 0x1a, 0xa9, 0x58, 0x08, 0x9b, 0xa0, 0x15, 0xc3, 0x32, 0xb6,
+  0xb7, 0xf1, 0xe8, 0xb9, 0xa5, 0x6f, 0xad, 0x37, 0xf6, 0x6e, 0x71, 0x1b,
+  0xb4, 0x75, 0x2d, 0x48, 0x5e, 0x9f, 0xc6, 0x15, 0xaa, 0x81, 0xef, 0xe5,
+  0xc4, 0x88, 0x95, 0x8a, 0x3a, 0x6c, 0x77, 0xcc, 0xb5, 0xcd, 0x65, 0xe4,
+  0x67, 0xe5, 0x73, 0xc9, 0x50, 0x52, 0x94, 0xc1, 0x27, 0x49, 0x3e, 0xa0,
+  0x6b, 0x41, 0x16, 0x41, 0xb6, 0x94, 0x99, 0x41, 0xae, 0x3e, 0xcb, 0xe2,
+  0x06, 0x46, 0x09, 0xe9, 0x4d, 0xbe, 0xc9, 0x4c, 0x55, 0xa9, 0x18, 0x7e,
+  0xa6, 0xdf, 0x6e, 0xfd, 0x4a, 0xb2, 0xcc, 0x6c, 0x4e, 0xd9, 0xc8, 0x50,
+  0x15, 0x93, 0xb3, 0xf2, 0xe9, 0xe3, 0xc2, 0x6a, 0xad, 0x3a, 0xd5, 0xfb,
+  0xc3, 0x79, 0x50, 0x9f, 0x25, 0x79, 0x29, 0xb2, 0x47, 0x64, 0x7c, 0x20,
+  0x3e, 0xe2, 0x08, 0x4d, 0x93, 0x29, 0x14, 0xb6, 0x34, 0x6e, 0xcf, 0x71,
+  0x46, 0x7e, 0x76, 0x10, 0xf4, 0xfd, 0x6c, 0xaa, 0x01, 0xd2, 0xc2, 0x06,
+  0xde, 0x92, 0x83, 0xcc, 0x58, 0x90, 0x2e, 0x92, 0xde, 0x1e, 0x65, 0xb7,
+  0x63, 0x2f, 0x3d, 0xb2, 0xeb, 0x70, 0x8c, 0x4c, 0xe0, 0xbe, 0x15, 0x9d,
+  0xde, 0xc1, 0x4d, 0x56, 0xf8, 0x0b, 0xc6, 0x8e, 0x07, 0xb9, 0x5d, 0xdf,
+  0x95, 0xf0, 0x7b, 0x40, 0x1f, 0x1a, 0x2c, 0xd7, 0x9c, 0x2b, 0x4b, 0x76,
+  0xf4, 0x59, 0xf5, 0x43, 0xc1, 0x2c, 0x66, 0x10, 0x9e, 0x9e, 0x66, 0x96,
+  0x60, 0x9d, 0x1c, 0x74, 0x1b, 0x4e, 0x18, 0x5c, 0x08, 0xb0, 0x6e, 0x6c,
+  0xca, 0x69, 0x1a, 0x02, 0xe9, 0xbb, 0xca, 0x78, 0xef, 0x66, 0x2e, 0xe3,
+  0x32, 0xfd, 0x41, 0x5c, 0x95, 0x74, 0x81, 0x4d, 0xf4, 0xda, 0xfe, 0x4b,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x3e, 0x30, 0x82, 0x01,
+  0x3a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d,
+  0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb6, 0xa8, 0xff,
+  0xa2, 0xa8, 0x2f, 0xd0, 0xa6, 0xcd, 0x4b, 0xb1, 0x68, 0xf3, 0xe7, 0x50,
+  0x10, 0x31, 0xa7, 0x79, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+  0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04,
+  0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x1a, 0x30, 0x44, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35,
+  0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+  0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46,
+  0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72,
+  0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x74, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x68, 0x30, 0x66, 0x30, 0x3d, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x31, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53,
+  0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+  0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x19,
+  0x53, 0xbf, 0x03, 0x3d, 0x9b, 0xe2, 0x6b, 0x5a, 0xfd, 0xba, 0x49, 0x1f,
+  0x4f, 0xec, 0xe1, 0xc6, 0x82, 0x39, 0x3c, 0xd2, 0x03, 0x04, 0x0f, 0xab,
+  0x7b, 0x3e, 0x82, 0xa9, 0x85, 0x10, 0x1f, 0xf4, 0xde, 0x32, 0xaf, 0x58,
+  0x3f, 0xff, 0x70, 0xf3, 0x30, 0x1d, 0x97, 0x2d, 0x4c, 0x9a, 0xe2, 0xec,
+  0x0c, 0x3e, 0x14, 0x2d, 0x2f, 0x98, 0x48, 0x9d, 0xae, 0x16, 0x6a, 0xac,
+  0x2d, 0x42, 0xaa, 0xb5, 0x64, 0xa4, 0x70, 0xbb, 0xeb, 0x73, 0x94, 0x7b,
+  0x46, 0x4c, 0xe7, 0x7a, 0x14, 0x76, 0x5b, 0x4c, 0x1d, 0x84, 0xa1, 0x20,
+  0x74, 0x1f, 0x2e, 0x4b, 0x5c, 0x70, 0x88, 0xdc, 0xbd, 0xf7, 0x19, 0x3d,
+  0xed, 0x59, 0x0d, 0xe2, 0x3f, 0x26, 0xe2, 0x9c, 0xac, 0xa4, 0x3c, 0x95,
+  0x1c, 0xf8, 0xbe, 0x8c, 0x03, 0xae, 0xf0, 0xe5, 0x9c, 0x4d, 0xbc, 0xc7,
+  0x9b, 0x58, 0x00, 0xbf, 0xaf, 0xad, 0xfa, 0x37, 0x6e, 0x71, 0x6d, 0x18,
+  0x34, 0x0e, 0xc1, 0xea, 0x6a, 0xf8, 0x0d, 0xdf, 0x69, 0x54, 0x56, 0x15,
+  0xf2, 0x28, 0xb3, 0xfe, 0xa4, 0x63, 0xec, 0xc5, 0x04, 0x64, 0x60, 0xbb,
+  0xfe, 0x2a, 0xf0, 0xf4, 0x87, 0xa1, 0xb0, 0xae, 0xbd, 0xaa, 0xe4, 0x2f,
+  0xe3, 0x03, 0x0b, 0x2f, 0x66, 0x5f, 0x85, 0xa4, 0x32, 0x7b, 0x46, 0xed,
+  0x25, 0x0c, 0xe7, 0xf1, 0xb7, 0xe7, 0x19, 0xfd, 0x60, 0xba, 0x5f, 0x87,
+  0x77, 0xde, 0x98, 0x07, 0x96, 0xe4, 0x5e, 0xea, 0x63, 0x7d, 0xa8, 0xde,
+  0x55, 0xda, 0x61, 0x5c, 0x3c, 0x90, 0x83, 0x43, 0x04, 0x07, 0x3c, 0xdd,
+  0xf3, 0xf8, 0x9f, 0x06, 0x52, 0x0a, 0xde, 0xc7, 0xb6, 0x7b, 0x8f, 0xe1,
+  0x11, 0xf7, 0x04, 0x7a, 0x35, 0xff, 0x6a, 0xbc, 0x5b, 0xc7, 0x50, 0x49,
+  0x08, 0x70, 0x6f, 0x94, 0x43, 0xcd, 0x9e, 0xc7, 0x70, 0xf1, 0xdb, 0xd0,
+  0x6d, 0xda, 0x8f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            10:e7:76:e8:a6:5a:6e:37:7e:05:03:06:d4:3c:25:ea
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Validity
+            Not Before: Apr 10 00:00:00 2006 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c3:dd:36:cc:83:c3:18:55:b0:96:d9:13:25:d3:
+                    26:86:48:38:bb:16:7f:f1:9f:29:f6:fd:03:f1:ed:
+                    4d:26:9a:56:f0:b5:1a:1a:cd:e6:cc:85:55:40:a4:
+                    b5:d0:0d:ca:22:ef:3d:23:c6:7e:6c:cc:bc:a1:e9:
+                    7c:50:46:e0:bd:14:ad:65:12:c2:0b:11:69:52:0a:
+                    07:92:1f:73:6f:c1:ba:d7:62:f0:ce:00:2e:34:a5:
+                    c8:e6:2f:0f:ec:0d:ea:44:61:75:68:e5:e4:dc:80:
+                    36:4f:da:78:5d:53:25:94:94:f5:4f:2e:3a:60:6f:
+                    0c:a6:d9:b3:f6:2a:2e:03:12:d5:26:42:07:51:b2:
+                    64:57:71:dc:21:1c:89:c7:69:a3:e6:fb:c2:7b:6e:
+                    ef:0c:87:fb:50:64:e8:4e:4b:ef:e7:71:9b:83:63:
+                    61:c9:32:8d:8c:ec:14:a7:e4:89:ad:3f:2b:26:64:
+                    e4:85:42:f2:89:50:e1:3a:be:15:e3:45:25:e2:5a:
+                    cb:8c:3f:e0:33:1e:35:09:5a:84:ea:7e:5d:a1:f5:
+                    91:80:0a:28:06:b7:cb:31:41:25:61:8b:01:e9:56:
+                    a2:f6:3e:5f:2f:f3:c4:43:f6:19:94:75:83:4c:a1:
+                    82:42:3a:c6:ba:c4:09:30:a6:e1:75:02:51:b9:5e:
+                    64:8b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+            X509v3 Subject Key Identifier: 
+                3C:41:E2:8F:08:08:A9:4C:25:89:8D:6D:C5:38:D0:FC:85:8C:62:17
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.782.1.2.1.3.1
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://www.usertrust.com/cacerts/UTNAddTrustServer_CA.crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         68:ab:fc:ef:80:6b:18:b2:b0:b3:a3:45:89:cb:53:c5:a2:e6:
+         af:08:a9:fd:ff:0f:49:ac:ff:e4:9f:d7:41:7c:a3:c5:a2:e8:
+         aa:e0:57:21:2d:c3:aa:7c:0c:4c:28:0b:79:f4:ee:4c:32:ad:
+         79:0e:7e:a2:5e:34:18:4f:df:54:f1:bd:68:7c:e3:d3:d7:46:
+         5e:6d:64:c2:f7:6d:88:82:73:0c:ef:99:85:ea:a9:ef:32:4a:
+         f0:83:9f:73:91:0c:a4:3e:2b:31:51:a6:62:8f:15:84:f9:a6:
+         3a:12:30:3f:da:6e:f8:cc:c7:19:92:0f:5c:f4:fe:17:f1:95:
+         08:47:52:2c:50:8f:e8:9b:a5:ee:ae:70:33:89:91:82:fe:30:
+         aa:76:76:59:d7:6c:18:d3:2b:12:5b:1d:28:1d:78:71:f6:cd:
+         36:a2:e9:07:48:44:3b:e7:57:6e:82:0a:ad:c5:8a:dd:e8:53:
+         b4:71:af:13:d2:06:9d:37:6d:53:3f:8a:35:08:fa:fe:a2:16:
+         e6:b9:6f:5c:56:39:d6:c6:aa:ef:19:67:ce:13:c5:b8:95:05:
+         fb:0a:44:c9:9f:a9:40:25:4b:32:11:af:07:fe:08:d5:42:71:
+         e9:e1:53:8b:15:1f:dd:2a:07:95:70:24:6f:64:5e:d3:b7:90:
+         2e:8b:21:d8
+-----BEGIN CERTIFICATE-----
+MIIEpjCCA46gAwIBAgIQEOd26KZabjd+BQMG1Dwl6jANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYwNDEwMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBiMQswCQYD
+VQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYD
+VQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD3TbMg8MYVbCW2RMl0yaGSDi7
+Fn/xnyn2/QPx7U0mmlbwtRoazebMhVVApLXQDcoi7z0jxn5szLyh6XxQRuC9FK1l
+EsILEWlSCgeSH3NvwbrXYvDOAC40pcjmLw/sDepEYXVo5eTcgDZP2nhdUyWUlPVP
+Ljpgbwym2bP2Ki4DEtUmQgdRsmRXcdwhHInHaaPm+8J7bu8Mh/tQZOhOS+/ncZuD
+Y2HJMo2M7BSn5ImtPysmZOSFQvKJUOE6vhXjRSXiWsuMP+AzHjUJWoTqfl2h9ZGA
+CigGt8sxQSVhiwHpVqL2Pl8v88RD9hmUdYNMoYJCOsa6xAkwpuF1AlG5XmSLAgMB
+AAGjggEgMIIBHDAfBgNVHSMEGDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNV
+HQ4EFgQUPEHijwgIqUwliY1txTjQ/IWMYhcwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud
+EwEB/wQIMAYBAf8CAQAwGQYDVR0gBBIwEDAOBgwrBgEEAYYOAQIBAwEwRAYDVR0f
+BD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly
+c3QtSGFyZHdhcmUuY3JsMFUGCCsGAQUFBwEBBEkwRzBFBggrBgEFBQcwAoY5aHR0
+cDovL3d3dy51c2VydHJ1c3QuY29tL2NhY2VydHMvVVROQWRkVHJ1c3RTZXJ2ZXJf
+Q0EuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQBoq/zvgGsYsrCzo0WJy1PFouavCKn9
+/w9JrP/kn9dBfKPFouiq4FchLcOqfAxMKAt59O5MMq15Dn6iXjQYT99U8b1ofOPT
+10ZebWTC922IgnMM75mF6qnvMkrwg59zkQykPisxUaZijxWE+aY6EjA/2m74zMcZ
+kg9c9P4X8ZUIR1IsUI/om6XurnAziZGC/jCqdnZZ12wY0ysSWx0oHXhx9s02oukH
+SEQ751duggqtxYrd6FO0ca8T0gadN21TP4o1CPr+ohbmuW9cVjnWxqrvGWfOE8W4
+lQX7CkTJn6lAJUsyEa8H/gjVQnHp4VOLFR/dKgeVcCRvZF7Tt5AuiyHY
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert48[] = {
+  0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x10, 0xe7, 0x76, 0xe8, 0xa6, 0x5a, 0x6e, 0x37, 0x7e,
+  0x05, 0x03, 0x06, 0xd4, 0x3c, 0x25, 0xea, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+  0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+  0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x36, 0x30, 0x34, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+  0x38, 0x33, 0x38, 0x5a, 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20,
+  0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41,
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xc3, 0xdd, 0x36, 0xcc, 0x83, 0xc3, 0x18,
+  0x55, 0xb0, 0x96, 0xd9, 0x13, 0x25, 0xd3, 0x26, 0x86, 0x48, 0x38, 0xbb,
+  0x16, 0x7f, 0xf1, 0x9f, 0x29, 0xf6, 0xfd, 0x03, 0xf1, 0xed, 0x4d, 0x26,
+  0x9a, 0x56, 0xf0, 0xb5, 0x1a, 0x1a, 0xcd, 0xe6, 0xcc, 0x85, 0x55, 0x40,
+  0xa4, 0xb5, 0xd0, 0x0d, 0xca, 0x22, 0xef, 0x3d, 0x23, 0xc6, 0x7e, 0x6c,
+  0xcc, 0xbc, 0xa1, 0xe9, 0x7c, 0x50, 0x46, 0xe0, 0xbd, 0x14, 0xad, 0x65,
+  0x12, 0xc2, 0x0b, 0x11, 0x69, 0x52, 0x0a, 0x07, 0x92, 0x1f, 0x73, 0x6f,
+  0xc1, 0xba, 0xd7, 0x62, 0xf0, 0xce, 0x00, 0x2e, 0x34, 0xa5, 0xc8, 0xe6,
+  0x2f, 0x0f, 0xec, 0x0d, 0xea, 0x44, 0x61, 0x75, 0x68, 0xe5, 0xe4, 0xdc,
+  0x80, 0x36, 0x4f, 0xda, 0x78, 0x5d, 0x53, 0x25, 0x94, 0x94, 0xf5, 0x4f,
+  0x2e, 0x3a, 0x60, 0x6f, 0x0c, 0xa6, 0xd9, 0xb3, 0xf6, 0x2a, 0x2e, 0x03,
+  0x12, 0xd5, 0x26, 0x42, 0x07, 0x51, 0xb2, 0x64, 0x57, 0x71, 0xdc, 0x21,
+  0x1c, 0x89, 0xc7, 0x69, 0xa3, 0xe6, 0xfb, 0xc2, 0x7b, 0x6e, 0xef, 0x0c,
+  0x87, 0xfb, 0x50, 0x64, 0xe8, 0x4e, 0x4b, 0xef, 0xe7, 0x71, 0x9b, 0x83,
+  0x63, 0x61, 0xc9, 0x32, 0x8d, 0x8c, 0xec, 0x14, 0xa7, 0xe4, 0x89, 0xad,
+  0x3f, 0x2b, 0x26, 0x64, 0xe4, 0x85, 0x42, 0xf2, 0x89, 0x50, 0xe1, 0x3a,
+  0xbe, 0x15, 0xe3, 0x45, 0x25, 0xe2, 0x5a, 0xcb, 0x8c, 0x3f, 0xe0, 0x33,
+  0x1e, 0x35, 0x09, 0x5a, 0x84, 0xea, 0x7e, 0x5d, 0xa1, 0xf5, 0x91, 0x80,
+  0x0a, 0x28, 0x06, 0xb7, 0xcb, 0x31, 0x41, 0x25, 0x61, 0x8b, 0x01, 0xe9,
+  0x56, 0xa2, 0xf6, 0x3e, 0x5f, 0x2f, 0xf3, 0xc4, 0x43, 0xf6, 0x19, 0x94,
+  0x75, 0x83, 0x4c, 0xa1, 0x82, 0x42, 0x3a, 0xc6, 0xba, 0xc4, 0x09, 0x30,
+  0xa6, 0xe1, 0x75, 0x02, 0x51, 0xb9, 0x5e, 0x64, 0x8b, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xa1,
+  0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5,
+  0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08,
+  0xa9, 0x4c, 0x25, 0x89, 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c,
+  0x62, 0x17, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+  0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+  0x01, 0x00, 0x30, 0x19, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30,
+  0x10, 0x30, 0x0e, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x86, 0x0e,
+  0x01, 0x02, 0x01, 0x03, 0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72,
+  0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x55, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x01, 0x01, 0x04, 0x49, 0x30, 0x47, 0x30, 0x45, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x39, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61,
+  0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
+  0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x68, 0xab, 0xfc, 0xef, 0x80, 0x6b, 0x18, 0xb2, 0xb0, 0xb3,
+  0xa3, 0x45, 0x89, 0xcb, 0x53, 0xc5, 0xa2, 0xe6, 0xaf, 0x08, 0xa9, 0xfd,
+  0xff, 0x0f, 0x49, 0xac, 0xff, 0xe4, 0x9f, 0xd7, 0x41, 0x7c, 0xa3, 0xc5,
+  0xa2, 0xe8, 0xaa, 0xe0, 0x57, 0x21, 0x2d, 0xc3, 0xaa, 0x7c, 0x0c, 0x4c,
+  0x28, 0x0b, 0x79, 0xf4, 0xee, 0x4c, 0x32, 0xad, 0x79, 0x0e, 0x7e, 0xa2,
+  0x5e, 0x34, 0x18, 0x4f, 0xdf, 0x54, 0xf1, 0xbd, 0x68, 0x7c, 0xe3, 0xd3,
+  0xd7, 0x46, 0x5e, 0x6d, 0x64, 0xc2, 0xf7, 0x6d, 0x88, 0x82, 0x73, 0x0c,
+  0xef, 0x99, 0x85, 0xea, 0xa9, 0xef, 0x32, 0x4a, 0xf0, 0x83, 0x9f, 0x73,
+  0x91, 0x0c, 0xa4, 0x3e, 0x2b, 0x31, 0x51, 0xa6, 0x62, 0x8f, 0x15, 0x84,
+  0xf9, 0xa6, 0x3a, 0x12, 0x30, 0x3f, 0xda, 0x6e, 0xf8, 0xcc, 0xc7, 0x19,
+  0x92, 0x0f, 0x5c, 0xf4, 0xfe, 0x17, 0xf1, 0x95, 0x08, 0x47, 0x52, 0x2c,
+  0x50, 0x8f, 0xe8, 0x9b, 0xa5, 0xee, 0xae, 0x70, 0x33, 0x89, 0x91, 0x82,
+  0xfe, 0x30, 0xaa, 0x76, 0x76, 0x59, 0xd7, 0x6c, 0x18, 0xd3, 0x2b, 0x12,
+  0x5b, 0x1d, 0x28, 0x1d, 0x78, 0x71, 0xf6, 0xcd, 0x36, 0xa2, 0xe9, 0x07,
+  0x48, 0x44, 0x3b, 0xe7, 0x57, 0x6e, 0x82, 0x0a, 0xad, 0xc5, 0x8a, 0xdd,
+  0xe8, 0x53, 0xb4, 0x71, 0xaf, 0x13, 0xd2, 0x06, 0x9d, 0x37, 0x6d, 0x53,
+  0x3f, 0x8a, 0x35, 0x08, 0xfa, 0xfe, 0xa2, 0x16, 0xe6, 0xb9, 0x6f, 0x5c,
+  0x56, 0x39, 0xd6, 0xc6, 0xaa, 0xef, 0x19, 0x67, 0xce, 0x13, 0xc5, 0xb8,
+  0x95, 0x05, 0xfb, 0x0a, 0x44, 0xc9, 0x9f, 0xa9, 0x40, 0x25, 0x4b, 0x32,
+  0x11, 0xaf, 0x07, 0xfe, 0x08, 0xd5, 0x42, 0x71, 0xe9, 0xe1, 0x53, 0x8b,
+  0x15, 0x1f, 0xdd, 0x2a, 0x07, 0x95, 0x70, 0x24, 0x6f, 0x64, 0x5e, 0xd3,
+  0xb7, 0x90, 0x2e, 0x8b, 0x21, 0xd8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            46:ea:f0:96:05:4c:c5:e3:fa:65:ea:6e:9f:42:c6:64
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Jun  7 08:09:10 2005 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:df:ee:58:10:a2:2b:6e:55:c4:8e:bf:2e:46:09:
+                    e7:e0:08:0f:2e:2b:7a:13:94:1b:bd:f6:b6:80:8e:
+                    65:05:93:00:1e:bc:af:e2:0f:8e:19:0d:12:47:ec:
+                    ac:ad:a3:fa:2e:70:f8:de:6e:fb:56:42:15:9e:2e:
+                    5c:ef:23:de:21:b9:05:76:27:19:0f:4f:d6:c3:9c:
+                    b4:be:94:19:63:f2:a6:11:0a:eb:53:48:9c:be:f2:
+                    29:3b:16:e8:1a:a0:4c:a6:c9:f4:18:59:68:c0:70:
+                    f2:53:00:c0:5e:50:82:a5:56:6f:36:f9:4a:e0:44:
+                    86:a0:4d:4e:d6:47:6e:49:4a:cb:67:d7:a6:c4:05:
+                    b9:8e:1e:f4:fc:ff:cd:e7:36:e0:9c:05:6c:b2:33:
+                    22:15:d0:b4:e0:cc:17:c0:b2:c0:f4:fe:32:3f:29:
+                    2a:95:7b:d8:f2:a7:4e:0f:54:7c:a1:0d:80:b3:09:
+                    03:c1:ff:5c:dd:5e:9a:3e:bc:ae:bc:47:8a:6a:ae:
+                    71:ca:1f:b1:2a:b8:5f:42:05:0b:ec:46:30:d1:72:
+                    0b:ca:e9:56:6d:f5:ef:df:78:be:61:ba:b2:a5:ae:
+                    04:4c:bc:a8:ac:69:15:97:bd:ef:eb:b4:8c:bf:35:
+                    f8:d4:c3:d1:28:0e:5c:3a:9f:70:18:33:20:77:c4:
+                    a2:af
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                53:32:D1:B3:CF:7F:FA:E0:F1:A0:5D:85:4E:92:D2:9E:45:1D:B4:4F
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/AddTrustExternalCARoot.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/AddTrustExternalCARoot.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         63:86:92:10:b1:13:fa:37:be:8e:2a:b6:1b:8a:43:f5:5c:ae:
+         0e:14:df:f7:69:40:7f:bf:1a:71:00:09:d8:bf:d4:24:4a:bf:
+         e0:93:ff:01:d8:0b:c6:0f:ec:7e:47:9c:b0:5d:f7:7c:14:9d:
+         fc:c0:33:92:84:5b:d2:83:f4:52:e2:22:58:74:fc:43:1b:3f:
+         a7:a3:58:da:03:fd:bc:f0:3a:e4:ed:cc:12:bb:c9:b9:ae:7b:
+         04:a0:04:72:bf:e9:de:2d:d2:a7:51:66:00:73:d2:bd:7e:aa:
+         9e:53:96:7d:69:b2:18:3e:8e:ad:56:50:7e:f7:d5:b0:ff:39:
+         62:65:82:8c:96:57:c3:8f:f7:60:f6:c2:8d:34:87:fc:4f:43:
+         e5:db:bf:1c:aa:f6:86:cd:e6:df:11:3f:8d:07:f7:6d:83:13:
+         c0:38:88:39:60:a1:7e:30:e1:e3:88:3e:a4:bb:63:6f:2c:e9:
+         8a:68:2c:ee:96:69:ac:04:61:e1:4f:4e:0e:9d:72:4c:f6:79:
+         38:c8:c7:48:69:6f:94:0f:74:b4:bc:c8:cf:57:4d:b9:75:71:
+         96:0d:8a:06:0b:eb:dd:d0:f0:3c:7d:c6:2e:98:46:6a:38:c7:
+         02:b5:c8:b8:b2:65:75:de:da:90:08:b6:77:b8:53:00:25:cb:
+         47:ca:73:5f
+-----BEGIN CERTIFICATE-----
+MIIEpjCCA46gAwIBAgIQRurwlgVMxeP6Zepun0LGZDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMRswGQYDVQQDExJVVE4gLSBEQVRBQ29y
+cCBTR0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf7lgQoituVcSO
+vy5GCefgCA8uK3oTlBu99raAjmUFkwAevK/iD44ZDRJH7Kyto/oucPjebvtWQhWe
+LlzvI94huQV2JxkPT9bDnLS+lBlj8qYRCutTSJy+8ik7FugaoEymyfQYWWjAcPJT
+AMBeUIKlVm82+UrgRIagTU7WR25JSstn16bEBbmOHvT8/83nNuCcBWyyMyIV0LTg
+zBfAssD0/jI/KSqVe9jyp04PVHyhDYCzCQPB/1zdXpo+vK68R4pqrnHKH7EquF9C
+BQvsRjDRcgvK6VZt9e/feL5hurKlrgRMvKisaRWXve/rtIy/NfjUw9EoDlw6n3AY
+MyB3xKKvAgMBAAGjggEXMIIBEzAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUUzLRs89/+uDxoF2FTpLSnkUdtE8wDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI
+AYb4QgQBMBEGA1UdIAQKMAgwBgYEVR0gADB7BgNVHR8EdDByMDigNqA0hjJodHRw
+Oi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2
+oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJv
+b3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBjhpIQsRP6N76OKrYbikP1XK4OFN/3
+aUB/vxpxAAnYv9QkSr/gk/8B2AvGD+x+R5ywXfd8FJ38wDOShFvSg/RS4iJYdPxD
+Gz+no1jaA/288Drk7cwSu8m5rnsEoARyv+neLdKnUWYAc9K9fqqeU5Z9abIYPo6t
+VlB+99Ww/zliZYKMllfDj/dg9sKNNIf8T0Pl278cqvaGzebfET+NB/dtgxPAOIg5
+YKF+MOHjiD6ku2NvLOmKaCzulmmsBGHhT04OnXJM9nk4yMdIaW+UD3S0vMjPV025
+dXGWDYoGC+vd0PA8fcYumEZqOMcCtci4smV13tqQCLZ3uFMAJctHynNf
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert49[] = {
+  0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x46, 0xea, 0xf0, 0x96, 0x05, 0x4c, 0xc5, 0xe3, 0xfa,
+  0x65, 0xea, 0x6e, 0x9f, 0x42, 0xc6, 0x64, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+  0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x93, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+  0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+  0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+  0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x55,
+  0x54, 0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, 0x72,
+  0x70, 0x20, 0x53, 0x47, 0x43, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xdf, 0xee, 0x58, 0x10, 0xa2, 0x2b, 0x6e, 0x55, 0xc4, 0x8e,
+  0xbf, 0x2e, 0x46, 0x09, 0xe7, 0xe0, 0x08, 0x0f, 0x2e, 0x2b, 0x7a, 0x13,
+  0x94, 0x1b, 0xbd, 0xf6, 0xb6, 0x80, 0x8e, 0x65, 0x05, 0x93, 0x00, 0x1e,
+  0xbc, 0xaf, 0xe2, 0x0f, 0x8e, 0x19, 0x0d, 0x12, 0x47, 0xec, 0xac, 0xad,
+  0xa3, 0xfa, 0x2e, 0x70, 0xf8, 0xde, 0x6e, 0xfb, 0x56, 0x42, 0x15, 0x9e,
+  0x2e, 0x5c, 0xef, 0x23, 0xde, 0x21, 0xb9, 0x05, 0x76, 0x27, 0x19, 0x0f,
+  0x4f, 0xd6, 0xc3, 0x9c, 0xb4, 0xbe, 0x94, 0x19, 0x63, 0xf2, 0xa6, 0x11,
+  0x0a, 0xeb, 0x53, 0x48, 0x9c, 0xbe, 0xf2, 0x29, 0x3b, 0x16, 0xe8, 0x1a,
+  0xa0, 0x4c, 0xa6, 0xc9, 0xf4, 0x18, 0x59, 0x68, 0xc0, 0x70, 0xf2, 0x53,
+  0x00, 0xc0, 0x5e, 0x50, 0x82, 0xa5, 0x56, 0x6f, 0x36, 0xf9, 0x4a, 0xe0,
+  0x44, 0x86, 0xa0, 0x4d, 0x4e, 0xd6, 0x47, 0x6e, 0x49, 0x4a, 0xcb, 0x67,
+  0xd7, 0xa6, 0xc4, 0x05, 0xb9, 0x8e, 0x1e, 0xf4, 0xfc, 0xff, 0xcd, 0xe7,
+  0x36, 0xe0, 0x9c, 0x05, 0x6c, 0xb2, 0x33, 0x22, 0x15, 0xd0, 0xb4, 0xe0,
+  0xcc, 0x17, 0xc0, 0xb2, 0xc0, 0xf4, 0xfe, 0x32, 0x3f, 0x29, 0x2a, 0x95,
+  0x7b, 0xd8, 0xf2, 0xa7, 0x4e, 0x0f, 0x54, 0x7c, 0xa1, 0x0d, 0x80, 0xb3,
+  0x09, 0x03, 0xc1, 0xff, 0x5c, 0xdd, 0x5e, 0x9a, 0x3e, 0xbc, 0xae, 0xbc,
+  0x47, 0x8a, 0x6a, 0xae, 0x71, 0xca, 0x1f, 0xb1, 0x2a, 0xb8, 0x5f, 0x42,
+  0x05, 0x0b, 0xec, 0x46, 0x30, 0xd1, 0x72, 0x0b, 0xca, 0xe9, 0x56, 0x6d,
+  0xf5, 0xef, 0xdf, 0x78, 0xbe, 0x61, 0xba, 0xb2, 0xa5, 0xae, 0x04, 0x4c,
+  0xbc, 0xa8, 0xac, 0x69, 0x15, 0x97, 0xbd, 0xef, 0xeb, 0xb4, 0x8c, 0xbf,
+  0x35, 0xf8, 0xd4, 0xc3, 0xd1, 0x28, 0x0e, 0x5c, 0x3a, 0x9f, 0x70, 0x18,
+  0x33, 0x20, 0x77, 0xc4, 0xa2, 0xaf, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x17, 0x30, 0x82, 0x01, 0x13, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a,
+  0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0,
+  0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x53, 0x32, 0xd1, 0xb3, 0xcf, 0x7f, 0xfa, 0xe0, 0xf1,
+  0xa0, 0x5d, 0x85, 0x4e, 0x92, 0xd2, 0x9e, 0x45, 0x1d, 0xb4, 0x4f, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20,
+  0x00, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72,
+  0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64,
+  0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+  0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x36,
+  0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x63, 0x86, 0x92, 0x10, 0xb1, 0x13, 0xfa, 0x37, 0xbe, 0x8e,
+  0x2a, 0xb6, 0x1b, 0x8a, 0x43, 0xf5, 0x5c, 0xae, 0x0e, 0x14, 0xdf, 0xf7,
+  0x69, 0x40, 0x7f, 0xbf, 0x1a, 0x71, 0x00, 0x09, 0xd8, 0xbf, 0xd4, 0x24,
+  0x4a, 0xbf, 0xe0, 0x93, 0xff, 0x01, 0xd8, 0x0b, 0xc6, 0x0f, 0xec, 0x7e,
+  0x47, 0x9c, 0xb0, 0x5d, 0xf7, 0x7c, 0x14, 0x9d, 0xfc, 0xc0, 0x33, 0x92,
+  0x84, 0x5b, 0xd2, 0x83, 0xf4, 0x52, 0xe2, 0x22, 0x58, 0x74, 0xfc, 0x43,
+  0x1b, 0x3f, 0xa7, 0xa3, 0x58, 0xda, 0x03, 0xfd, 0xbc, 0xf0, 0x3a, 0xe4,
+  0xed, 0xcc, 0x12, 0xbb, 0xc9, 0xb9, 0xae, 0x7b, 0x04, 0xa0, 0x04, 0x72,
+  0xbf, 0xe9, 0xde, 0x2d, 0xd2, 0xa7, 0x51, 0x66, 0x00, 0x73, 0xd2, 0xbd,
+  0x7e, 0xaa, 0x9e, 0x53, 0x96, 0x7d, 0x69, 0xb2, 0x18, 0x3e, 0x8e, 0xad,
+  0x56, 0x50, 0x7e, 0xf7, 0xd5, 0xb0, 0xff, 0x39, 0x62, 0x65, 0x82, 0x8c,
+  0x96, 0x57, 0xc3, 0x8f, 0xf7, 0x60, 0xf6, 0xc2, 0x8d, 0x34, 0x87, 0xfc,
+  0x4f, 0x43, 0xe5, 0xdb, 0xbf, 0x1c, 0xaa, 0xf6, 0x86, 0xcd, 0xe6, 0xdf,
+  0x11, 0x3f, 0x8d, 0x07, 0xf7, 0x6d, 0x83, 0x13, 0xc0, 0x38, 0x88, 0x39,
+  0x60, 0xa1, 0x7e, 0x30, 0xe1, 0xe3, 0x88, 0x3e, 0xa4, 0xbb, 0x63, 0x6f,
+  0x2c, 0xe9, 0x8a, 0x68, 0x2c, 0xee, 0x96, 0x69, 0xac, 0x04, 0x61, 0xe1,
+  0x4f, 0x4e, 0x0e, 0x9d, 0x72, 0x4c, 0xf6, 0x79, 0x38, 0xc8, 0xc7, 0x48,
+  0x69, 0x6f, 0x94, 0x0f, 0x74, 0xb4, 0xbc, 0xc8, 0xcf, 0x57, 0x4d, 0xb9,
+  0x75, 0x71, 0x96, 0x0d, 0x8a, 0x06, 0x0b, 0xeb, 0xdd, 0xd0, 0xf0, 0x3c,
+  0x7d, 0xc6, 0x2e, 0x98, 0x46, 0x6a, 0x38, 0xc7, 0x02, 0xb5, 0xc8, 0xb8,
+  0xb2, 0x65, 0x75, 0xde, 0xda, 0x90, 0x08, 0xb6, 0x77, 0xb8, 0x53, 0x00,
+  0x25, 0xcb, 0x47, 0xca, 0x73, 0x5f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            2e:79:83:2e:90:88:87:ea:8b:8e:f3:1a:6e:e6:7a:44
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC
+        Validity
+            Not Before: Dec  1 00:00:00 2006 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+                    98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+                    e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+                    b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+                    86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+                    da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+                    d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+                    7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+                    9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+                    30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+                    77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+                    02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+                    c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+                    00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+                    0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+                    2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+                    47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+                    26:f7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:53:32:D1:B3:CF:7F:FA:E0:F1:A0:5D:85:4E:92:D2:9E:45:1D:B4:4F
+
+            X509v3 Subject Key Identifier: 
+                0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/UTN-DATACorpSGC.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/UTN-DATACorpSGC.crl
+
+    Signature Algorithm: sha1WithRSAEncryption
+         d8:5e:92:c4:ae:14:dc:43:ad:c2:a4:c3:67:45:07:1d:f9:37:
+         a2:19:c7:1c:37:35:91:13:1c:07:c4:7d:42:a6:0e:f0:86:5c:
+         43:6b:0e:44:cf:be:24:61:3a:42:a9:ce:9d:4c:af:79:39:70:
+         dd:0e:04:20:4e:95:9c:3c:de:b7:60:ba:63:43:40:ed:6a:0f:
+         81:49:46:bb:1e:93:c0:4b:f3:f8:e1:36:49:1b:6f:b6:0c:0d:
+         f2:90:57:8a:fc:6d:93:f2:28:c7:fa:86:0a:28:b3:17:0e:59:
+         8a:2e:b6:bf:cd:e1:ac:4c:66:6c:f2:55:91:56:b7:32:bf:b1:
+         e4:7d:b5:e8:3a:b6:2f:db:b2:9c:da:50:93:8e:4e:c5:ac:9a:
+         7e:5c:9e:12:3c:3b:4d:c6:50:70:b3:65:2b:8e:f7:6b:a1:bb:
+         25:c0:00:bb:f5:ec:16:65:81:0e:fb:d4:a3:21:96:77:9a:a8:
+         74:bc:53:aa:c2:39:50:ff:0b:02:09:61:cc:95:b7:d7:88:6a:
+         f6:5c:c5:68:d3:14:95:1a:47:5f:d9:fb:2d:e4:2f:8f:13:86:
+         ab:31:13:40:13:ac:6e:ed:b5:10:30:8b:1b:50:a9:ce:ee:8c:
+         ca:eb:7c:b5:b9:16:3d:d4:fa:6f:92:6d:1e:a2:bd:fb:02:4a:
+         c5:70:be:f1
+-----BEGIN CERTIFICATE-----
+MIIEqzCCA5OgAwIBAgIQLnmDLpCIh+qLjvMabuZ6RDANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw0wNjEyMDEwMDAwMDBaFw0yMDA1MzAxMDQ4MzhaMIGBMQswCQYDVQQG
+EwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm
+b3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RP
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZ
+rts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAh
+TaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23Iw
+ambV4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVD
+iOEjPqXSJDlqR6sA1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ
+0o7KBWFxB3NH5YoZEr0ETc5OnKVIrLsm9wIDAQABo4IBCTCCAQUwHwYDVR0jBBgw
+FoAUUzLRs89/+uDxoF2FTpLSnkUdtE8wHQYDVR0OBBYEFAtY5YvGTBU3pECpMKkh
+vkc2Wlb/MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MCAGA1UdJQQZ
+MBcGCisGAQQBgjcKAwMGCWCGSAGG+EIEATARBgNVHSAECjAIMAYGBFUdIAAwbQYD
+VR0fBGYwZDAxoC+gLYYraHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLURBVEFD
+b3JwU0dDLmNybDAvoC2gK4YpaHR0cDovL2NybC5jb21vZG8ubmV0L1VUTi1EQVRB
+Q29ycFNHQy5jcmwwDQYJKoZIhvcNAQEFBQADggEBANheksSuFNxDrcKkw2dFBx35
+N6IZxxw3NZETHAfEfUKmDvCGXENrDkTPviRhOkKpzp1Mr3k5cN0OBCBOlZw83rdg
+umNDQO1qD4FJRrsek8BL8/jhNkkbb7YMDfKQV4r8bZPyKMf6hgoosxcOWYoutr/N
+4axMZmzyVZFWtzK/seR9teg6ti/bspzaUJOOTsWsmn5cnhI8O03GUHCzZSuO92uh
+uyXAALv17BZlgQ771KMhlneaqHS8U6rCOVD/CwIJYcyVt9eIavZcxWjTFJUaR1/Z
++y3kL48ThqsxE0ATrG7ttRAwixtQqc7ujMrrfLW5Fj3U+m+SbR6ivfsCSsVwvvE=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert50[] = {
+  0x30, 0x82, 0x04, 0xab, 0x30, 0x82, 0x03, 0x93, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x2e, 0x79, 0x83, 0x2e, 0x90, 0x88, 0x87, 0xea, 0x8b,
+  0x8e, 0xf3, 0x1a, 0x6e, 0xe6, 0x7a, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x93, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x55, 0x54,
+  0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, 0x72, 0x70,
+  0x20, 0x53, 0x47, 0x43, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32,
+  0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32,
+  0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a,
+  0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04,
+  0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d,
+  0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66,
+  0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20,
+  0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f,
+  0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+  0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+  0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b,
+  0x8b, 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98,
+  0xd3, 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88,
+  0xce, 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99,
+  0xae, 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42,
+  0x91, 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb,
+  0x59, 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda,
+  0x37, 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21,
+  0x4d, 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2,
+  0x83, 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae,
+  0x34, 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1,
+  0xf9, 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30,
+  0x6a, 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04,
+  0x48, 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc,
+  0x39, 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37,
+  0x9c, 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43,
+  0x88, 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00,
+  0xd4, 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3,
+  0x51, 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05,
+  0x65, 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19,
+  0xd2, 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19,
+  0x12, 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26,
+  0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x09, 0x30, 0x82,
+  0x01, 0x05, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+  0x16, 0x80, 0x14, 0x53, 0x32, 0xd1, 0xb3, 0xcf, 0x7f, 0xfa, 0xe0, 0xf1,
+  0xa0, 0x5d, 0x85, 0x4e, 0x92, 0xd2, 0x9e, 0x45, 0x1d, 0xb4, 0x4f, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58,
+  0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21,
+  0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+  0x01, 0x01, 0xff, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x19,
+  0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a,
+  0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+  0x01, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+  0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x6d, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x66, 0x30, 0x64, 0x30, 0x31, 0xa0, 0x2f, 0xa0,
+  0x2d, 0x86, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x44, 0x41, 0x54, 0x41, 0x43,
+  0x6f, 0x72, 0x70, 0x53, 0x47, 0x43, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x2f,
+  0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x44, 0x41, 0x54, 0x41,
+  0x43, 0x6f, 0x72, 0x70, 0x53, 0x47, 0x43, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd8, 0x5e, 0x92, 0xc4, 0xae,
+  0x14, 0xdc, 0x43, 0xad, 0xc2, 0xa4, 0xc3, 0x67, 0x45, 0x07, 0x1d, 0xf9,
+  0x37, 0xa2, 0x19, 0xc7, 0x1c, 0x37, 0x35, 0x91, 0x13, 0x1c, 0x07, 0xc4,
+  0x7d, 0x42, 0xa6, 0x0e, 0xf0, 0x86, 0x5c, 0x43, 0x6b, 0x0e, 0x44, 0xcf,
+  0xbe, 0x24, 0x61, 0x3a, 0x42, 0xa9, 0xce, 0x9d, 0x4c, 0xaf, 0x79, 0x39,
+  0x70, 0xdd, 0x0e, 0x04, 0x20, 0x4e, 0x95, 0x9c, 0x3c, 0xde, 0xb7, 0x60,
+  0xba, 0x63, 0x43, 0x40, 0xed, 0x6a, 0x0f, 0x81, 0x49, 0x46, 0xbb, 0x1e,
+  0x93, 0xc0, 0x4b, 0xf3, 0xf8, 0xe1, 0x36, 0x49, 0x1b, 0x6f, 0xb6, 0x0c,
+  0x0d, 0xf2, 0x90, 0x57, 0x8a, 0xfc, 0x6d, 0x93, 0xf2, 0x28, 0xc7, 0xfa,
+  0x86, 0x0a, 0x28, 0xb3, 0x17, 0x0e, 0x59, 0x8a, 0x2e, 0xb6, 0xbf, 0xcd,
+  0xe1, 0xac, 0x4c, 0x66, 0x6c, 0xf2, 0x55, 0x91, 0x56, 0xb7, 0x32, 0xbf,
+  0xb1, 0xe4, 0x7d, 0xb5, 0xe8, 0x3a, 0xb6, 0x2f, 0xdb, 0xb2, 0x9c, 0xda,
+  0x50, 0x93, 0x8e, 0x4e, 0xc5, 0xac, 0x9a, 0x7e, 0x5c, 0x9e, 0x12, 0x3c,
+  0x3b, 0x4d, 0xc6, 0x50, 0x70, 0xb3, 0x65, 0x2b, 0x8e, 0xf7, 0x6b, 0xa1,
+  0xbb, 0x25, 0xc0, 0x00, 0xbb, 0xf5, 0xec, 0x16, 0x65, 0x81, 0x0e, 0xfb,
+  0xd4, 0xa3, 0x21, 0x96, 0x77, 0x9a, 0xa8, 0x74, 0xbc, 0x53, 0xaa, 0xc2,
+  0x39, 0x50, 0xff, 0x0b, 0x02, 0x09, 0x61, 0xcc, 0x95, 0xb7, 0xd7, 0x88,
+  0x6a, 0xf6, 0x5c, 0xc5, 0x68, 0xd3, 0x14, 0x95, 0x1a, 0x47, 0x5f, 0xd9,
+  0xfb, 0x2d, 0xe4, 0x2f, 0x8f, 0x13, 0x86, 0xab, 0x31, 0x13, 0x40, 0x13,
+  0xac, 0x6e, 0xed, 0xb5, 0x10, 0x30, 0x8b, 0x1b, 0x50, 0xa9, 0xce, 0xee,
+  0x8c, 0xca, 0xeb, 0x7c, 0xb5, 0xb9, 0x16, 0x3d, 0xd4, 0xfa, 0x6f, 0x92,
+  0x6d, 0x1e, 0xa2, 0xbd, 0xfb, 0x02, 0x4a, 0xc5, 0x70, 0xbe, 0xf1,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            7f:71:c1:d3:a2:26:b0:d2:b1:13:f3:e6:81:67:64:3e
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Dec  7 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Internet2, OU=InCommon, CN=InCommon Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:97:7c:c7:c8:fe:b3:e9:20:6a:a3:a4:4f:8e:8e:
+                    34:56:06:b3:7a:6c:aa:10:9b:48:61:2b:36:90:69:
+                    e3:34:0a:47:a7:bb:7b:de:aa:6a:fb:eb:82:95:8f:
+                    ca:1d:7f:af:75:a6:a8:4c:da:20:67:61:1a:0d:86:
+                    c1:ca:c1:87:af:ac:4e:e4:de:62:1b:2f:9d:b1:98:
+                    af:c6:01:fb:17:70:db:ac:14:59:ec:6f:3f:33:7f:
+                    a6:98:0b:e4:e2:38:af:f5:7f:85:6d:0e:74:04:9d:
+                    f6:27:86:c7:9b:8f:e7:71:2a:08:f4:03:02:40:63:
+                    24:7d:40:57:8f:54:e0:54:7e:b6:13:48:61:f1:de:
+                    ce:0e:bd:b6:fa:4d:98:b2:d9:0d:8d:79:a6:e0:aa:
+                    cd:0c:91:9a:a5:df:ab:73:bb:ca:14:78:5c:47:29:
+                    a1:ca:c5:ba:9f:c7:da:60:f7:ff:e7:7f:f2:d9:da:
+                    a1:2d:0f:49:16:a7:d3:00:92:cf:8a:47:d9:4d:f8:
+                    d5:95:66:d3:74:f9:80:63:00:4f:4c:84:16:1f:b3:
+                    f5:24:1f:a1:4e:de:e8:95:d6:b2:0b:09:8b:2c:6b:
+                    c7:5c:2f:8c:63:c9:99:cb:52:b1:62:7b:73:01:62:
+                    7f:63:6c:d8:68:a0:ee:6a:a8:8d:1f:29:f3:d0:18:
+                    ac:ad
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                48:4F:5A:FA:2F:4A:9A:5E:E0:50:F3:6B:7B:55:A5:DE:F5:BE:34:5D
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         93:66:21:80:74:45:85:4b:c2:ab:ce:32:b0:29:fe:dd:df:d6:
+         24:5b:bf:03:6a:6f:50:3e:0e:1b:b3:0d:88:a3:5b:ee:c4:a4:
+         12:3b:56:ef:06:7f:cf:7f:21:95:56:3b:41:31:fe:e1:aa:93:
+         d2:95:f3:95:0d:3c:47:ab:ca:5c:26:ad:3e:f1:f9:8c:34:6e:
+         11:be:f4:67:e3:02:49:f9:a6:7c:7b:64:25:dd:17:46:f2:50:
+         e3:e3:0a:21:3a:49:24:cd:c6:84:65:68:67:68:b0:45:2d:47:
+         99:cd:9c:ab:86:29:11:72:dc:d6:9c:36:43:74:f3:d4:97:9e:
+         56:a0:fe:5f:40:58:d2:d5:d7:7e:7c:c5:8e:1a:b2:04:5c:92:
+         66:0e:85:ad:2e:06:ce:c8:a3:d8:eb:14:27:91:de:cf:17:30:
+         81:53:b6:66:12:ad:37:e4:f5:ef:96:5c:20:0e:36:e9:ac:62:
+         7d:19:81:8a:f5:90:61:a6:49:ab:ce:3c:df:e6:ca:64:ee:82:
+         65:39:45:95:16:ba:41:06:00:98:ba:0c:56:61:e4:c6:c6:86:
+         01:cf:66:a9:22:29:02:d6:3d:cf:c4:2a:8d:99:de:fb:09:14:
+         9e:0e:d1:d5:c6:d7:81:dd:ad:24:ab:ac:07:05:e2:1d:68:c3:
+         70:66:5f:d3
+-----BEGIN CERTIFICATE-----
+MIIEwzCCA6ugAwIBAgIQf3HB06ImsNKxE/PmgWdkPjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMTIwNzAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+UTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5D
+b21tb24xGzAZBgNVBAMTEkluQ29tbW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAJd8x8j+s+kgaqOkT46ONFYGs3psqhCbSGErNpBp
+4zQKR6e7e96qavvrgpWPyh1/r3WmqEzaIGdhGg2GwcrBh6+sTuTeYhsvnbGYr8YB
++xdw26wUWexvPzN/ppgL5OI4r/V/hW0OdASd9ieGx5uP53EqCPQDAkBjJH1AV49U
+4FR+thNIYfHezg69tvpNmLLZDY15puCqzQyRmqXfq3O7yhR4XEcpocrFup/H2mD3
+/+d/8tnaoS0PSRan0wCSz4pH2U341ZVm03T5gGMAT0yEFh+z9SQfoU7e6JXWsgsJ
+iyxrx1wvjGPJmctSsWJ7cwFif2Ns2Gig7mqojR8p89AYrK0CAwEAAaOCAXcwggFz
+MB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBRIT1r6
+L0qaXuBQ82t7VaXe9b40XTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDov
+L2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGz
+BggrBgEFBQcBAQSBpjCBozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1
+c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1o
+dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYI
+KwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEF
+BQADggEBAJNmIYB0RYVLwqvOMrAp/t3f1iRbvwNqb1A+DhuzDYijW+7EpBI7Vu8G
+f89/IZVWO0Ex/uGqk9KV85UNPEerylwmrT7x+Yw0bhG+9GfjAkn5pnx7ZCXdF0by
+UOPjCiE6SSTNxoRlaGdosEUtR5nNnKuGKRFy3NacNkN089SXnlag/l9AWNLV1358
+xY4asgRckmYOha0uBs7Io9jrFCeR3s8XMIFTtmYSrTfk9e+WXCAONumsYn0ZgYr1
+kGGmSavOPN/mymTugmU5RZUWukEGAJi6DFZh5MbGhgHPZqkiKQLWPc/EKo2Z3vsJ
+FJ4O0dXG14HdrSSrrAcF4h1ow3BmX9M=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert51[] = {
+  0x30, 0x82, 0x04, 0xc3, 0x30, 0x82, 0x03, 0xab, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x7f, 0x71, 0xc1, 0xd3, 0xa2, 0x26, 0xb0, 0xd2, 0xb1,
+  0x13, 0xf3, 0xe6, 0x81, 0x67, 0x64, 0x3e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x32, 0x30,
+  0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x51, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x32, 0x31, 0x11,
+  0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x08, 0x49, 0x6e, 0x43,
+  0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x12, 0x49, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+  0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82,
+  0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+  0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0x7c, 0xc7, 0xc8, 0xfe,
+  0xb3, 0xe9, 0x20, 0x6a, 0xa3, 0xa4, 0x4f, 0x8e, 0x8e, 0x34, 0x56, 0x06,
+  0xb3, 0x7a, 0x6c, 0xaa, 0x10, 0x9b, 0x48, 0x61, 0x2b, 0x36, 0x90, 0x69,
+  0xe3, 0x34, 0x0a, 0x47, 0xa7, 0xbb, 0x7b, 0xde, 0xaa, 0x6a, 0xfb, 0xeb,
+  0x82, 0x95, 0x8f, 0xca, 0x1d, 0x7f, 0xaf, 0x75, 0xa6, 0xa8, 0x4c, 0xda,
+  0x20, 0x67, 0x61, 0x1a, 0x0d, 0x86, 0xc1, 0xca, 0xc1, 0x87, 0xaf, 0xac,
+  0x4e, 0xe4, 0xde, 0x62, 0x1b, 0x2f, 0x9d, 0xb1, 0x98, 0xaf, 0xc6, 0x01,
+  0xfb, 0x17, 0x70, 0xdb, 0xac, 0x14, 0x59, 0xec, 0x6f, 0x3f, 0x33, 0x7f,
+  0xa6, 0x98, 0x0b, 0xe4, 0xe2, 0x38, 0xaf, 0xf5, 0x7f, 0x85, 0x6d, 0x0e,
+  0x74, 0x04, 0x9d, 0xf6, 0x27, 0x86, 0xc7, 0x9b, 0x8f, 0xe7, 0x71, 0x2a,
+  0x08, 0xf4, 0x03, 0x02, 0x40, 0x63, 0x24, 0x7d, 0x40, 0x57, 0x8f, 0x54,
+  0xe0, 0x54, 0x7e, 0xb6, 0x13, 0x48, 0x61, 0xf1, 0xde, 0xce, 0x0e, 0xbd,
+  0xb6, 0xfa, 0x4d, 0x98, 0xb2, 0xd9, 0x0d, 0x8d, 0x79, 0xa6, 0xe0, 0xaa,
+  0xcd, 0x0c, 0x91, 0x9a, 0xa5, 0xdf, 0xab, 0x73, 0xbb, 0xca, 0x14, 0x78,
+  0x5c, 0x47, 0x29, 0xa1, 0xca, 0xc5, 0xba, 0x9f, 0xc7, 0xda, 0x60, 0xf7,
+  0xff, 0xe7, 0x7f, 0xf2, 0xd9, 0xda, 0xa1, 0x2d, 0x0f, 0x49, 0x16, 0xa7,
+  0xd3, 0x00, 0x92, 0xcf, 0x8a, 0x47, 0xd9, 0x4d, 0xf8, 0xd5, 0x95, 0x66,
+  0xd3, 0x74, 0xf9, 0x80, 0x63, 0x00, 0x4f, 0x4c, 0x84, 0x16, 0x1f, 0xb3,
+  0xf5, 0x24, 0x1f, 0xa1, 0x4e, 0xde, 0xe8, 0x95, 0xd6, 0xb2, 0x0b, 0x09,
+  0x8b, 0x2c, 0x6b, 0xc7, 0x5c, 0x2f, 0x8c, 0x63, 0xc9, 0x99, 0xcb, 0x52,
+  0xb1, 0x62, 0x7b, 0x73, 0x01, 0x62, 0x7f, 0x63, 0x6c, 0xd8, 0x68, 0xa0,
+  0xee, 0x6a, 0xa8, 0x8d, 0x1f, 0x29, 0xf3, 0xd0, 0x18, 0xac, 0xad, 0x02,
+  0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26,
+  0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x48, 0x4f, 0x5a, 0xfa,
+  0x2f, 0x4a, 0x9a, 0x5e, 0xe0, 0x50, 0xf3, 0x6b, 0x7b, 0x55, 0xa5, 0xde,
+  0xf5, 0xbe, 0x34, 0x5d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30,
+  0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39,
+  0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43,
+  0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81,
+  0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43,
+  0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53,
+  0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x93, 0x66, 0x21, 0x80, 0x74,
+  0x45, 0x85, 0x4b, 0xc2, 0xab, 0xce, 0x32, 0xb0, 0x29, 0xfe, 0xdd, 0xdf,
+  0xd6, 0x24, 0x5b, 0xbf, 0x03, 0x6a, 0x6f, 0x50, 0x3e, 0x0e, 0x1b, 0xb3,
+  0x0d, 0x88, 0xa3, 0x5b, 0xee, 0xc4, 0xa4, 0x12, 0x3b, 0x56, 0xef, 0x06,
+  0x7f, 0xcf, 0x7f, 0x21, 0x95, 0x56, 0x3b, 0x41, 0x31, 0xfe, 0xe1, 0xaa,
+  0x93, 0xd2, 0x95, 0xf3, 0x95, 0x0d, 0x3c, 0x47, 0xab, 0xca, 0x5c, 0x26,
+  0xad, 0x3e, 0xf1, 0xf9, 0x8c, 0x34, 0x6e, 0x11, 0xbe, 0xf4, 0x67, 0xe3,
+  0x02, 0x49, 0xf9, 0xa6, 0x7c, 0x7b, 0x64, 0x25, 0xdd, 0x17, 0x46, 0xf2,
+  0x50, 0xe3, 0xe3, 0x0a, 0x21, 0x3a, 0x49, 0x24, 0xcd, 0xc6, 0x84, 0x65,
+  0x68, 0x67, 0x68, 0xb0, 0x45, 0x2d, 0x47, 0x99, 0xcd, 0x9c, 0xab, 0x86,
+  0x29, 0x11, 0x72, 0xdc, 0xd6, 0x9c, 0x36, 0x43, 0x74, 0xf3, 0xd4, 0x97,
+  0x9e, 0x56, 0xa0, 0xfe, 0x5f, 0x40, 0x58, 0xd2, 0xd5, 0xd7, 0x7e, 0x7c,
+  0xc5, 0x8e, 0x1a, 0xb2, 0x04, 0x5c, 0x92, 0x66, 0x0e, 0x85, 0xad, 0x2e,
+  0x06, 0xce, 0xc8, 0xa3, 0xd8, 0xeb, 0x14, 0x27, 0x91, 0xde, 0xcf, 0x17,
+  0x30, 0x81, 0x53, 0xb6, 0x66, 0x12, 0xad, 0x37, 0xe4, 0xf5, 0xef, 0x96,
+  0x5c, 0x20, 0x0e, 0x36, 0xe9, 0xac, 0x62, 0x7d, 0x19, 0x81, 0x8a, 0xf5,
+  0x90, 0x61, 0xa6, 0x49, 0xab, 0xce, 0x3c, 0xdf, 0xe6, 0xca, 0x64, 0xee,
+  0x82, 0x65, 0x39, 0x45, 0x95, 0x16, 0xba, 0x41, 0x06, 0x00, 0x98, 0xba,
+  0x0c, 0x56, 0x61, 0xe4, 0xc6, 0xc6, 0x86, 0x01, 0xcf, 0x66, 0xa9, 0x22,
+  0x29, 0x02, 0xd6, 0x3d, 0xcf, 0xc4, 0x2a, 0x8d, 0x99, 0xde, 0xfb, 0x09,
+  0x14, 0x9e, 0x0e, 0xd1, 0xd5, 0xc6, 0xd7, 0x81, 0xdd, 0xad, 0x24, 0xab,
+  0xac, 0x07, 0x05, 0xe2, 0x1d, 0x68, 0xc3, 0x70, 0x66, 0x5f, 0xd3,
+};
diff --git a/net/quic/crypto/common_cert_set_51_100.inc b/net/quic/crypto/common_cert_set_51_100.inc
new file mode 100644
index 0000000..3c79622
--- /dev/null
+++ b/net/quic/crypto/common_cert_set_51_100.inc
@@ -0,0 +1,11308 @@
+/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            35:97:31:87:f3:87:3a:07:32:7e:ce:58:0c:9b:7e:da
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2021 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+                    4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+                    08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+                    2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+                    8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+                    a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+                    54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+                    d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+                    7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+                    bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+                    f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+                    ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+                    f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+                    21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+                    63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+                    ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+                    9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+                    25:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 Subject Key Identifier: 
+                7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+            X509v3 Extended Key Usage: 
+                Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         0f:25:ae:48:ed:1b:33:85:4c:0c:b5:c2:d7:fe:4d:d6:83:28:
+         4c:41:65:60:00:0b:77:48:71:82:fe:7f:db:5a:0e:20:cc:d2:
+         ea:47:bc:64:42:61:44:34:74:30:81:81:26:8a:4a:f7:44:5d:
+         7e:34:80:a8:b8:83:e2:09:d7:6d:23:dd:89:ed:28:08:bd:63:
+         5a:11:57:08:c4:9e:da:e2:68:28:af:dd:50:3c:ec:82:21:d8:
+         00:c2:55:44:50:70:41:ad:83:17:79:ba:08:f3:2b:de:ed:34:
+         1d:44:9e:d2:04:93:f4:cb:05:17:2d:09:2d:2d:63:ef:f6:26:
+         0b:7b
+-----BEGIN CERTIFICATE-----
+MIIExjCCBC+gAwIBAgIQNZcxh/OHOgcyfs5YDJt+2jANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggGRMIIBjTAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwNAYD
+VR0lBC0wKwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBBggrBgEFBQcDAQYIKwYBBQUH
+AwIwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUr
+DgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNp
+Z24uY29tL3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhho
+dHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wDQYJKoZIhvcNAQEFBQADgYEADyWuSO0b
+M4VMDLXC1/5N1oMoTEFlYAALd0hxgv5/21oOIMzS6ke8ZEJhRDR0MIGBJopK90Rd
+fjSAqLiD4gnXbSPdie0oCL1jWhFXCMSe2uJoKK/dUDzsgiHYAMJVRFBwQa2DF3m6
+CPMr3u00HUSe0gST9MsFFy0JLS1j7/YmC3s=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert52[] = {
+  0x30, 0x82, 0x04, 0xc6, 0x30, 0x82, 0x04, 0x2f, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x35, 0x97, 0x31, 0x87, 0xf3, 0x87, 0x3a, 0x07, 0x32,
+  0x7e, 0xce, 0x58, 0x0c, 0x9b, 0x7e, 0xda, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+  0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+  0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+  0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+  0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+  0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+  0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+  0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+  0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+  0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+  0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+  0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+  0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+  0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+  0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+  0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+  0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+  0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+  0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+  0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+  0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+  0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+  0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+  0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+  0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+  0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+  0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+  0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+  0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+  0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x8d, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+  0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+  0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+  0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+  0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+  0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x34, 0x06, 0x03,
+  0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01,
+  0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x02, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+  0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+  0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+  0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+  0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+  0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+  0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76,
+  0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x0f, 0x25, 0xae, 0x48, 0xed, 0x1b,
+  0x33, 0x85, 0x4c, 0x0c, 0xb5, 0xc2, 0xd7, 0xfe, 0x4d, 0xd6, 0x83, 0x28,
+  0x4c, 0x41, 0x65, 0x60, 0x00, 0x0b, 0x77, 0x48, 0x71, 0x82, 0xfe, 0x7f,
+  0xdb, 0x5a, 0x0e, 0x20, 0xcc, 0xd2, 0xea, 0x47, 0xbc, 0x64, 0x42, 0x61,
+  0x44, 0x34, 0x74, 0x30, 0x81, 0x81, 0x26, 0x8a, 0x4a, 0xf7, 0x44, 0x5d,
+  0x7e, 0x34, 0x80, 0xa8, 0xb8, 0x83, 0xe2, 0x09, 0xd7, 0x6d, 0x23, 0xdd,
+  0x89, 0xed, 0x28, 0x08, 0xbd, 0x63, 0x5a, 0x11, 0x57, 0x08, 0xc4, 0x9e,
+  0xda, 0xe2, 0x68, 0x28, 0xaf, 0xdd, 0x50, 0x3c, 0xec, 0x82, 0x21, 0xd8,
+  0x00, 0xc2, 0x55, 0x44, 0x50, 0x70, 0x41, 0xad, 0x83, 0x17, 0x79, 0xba,
+  0x08, 0xf3, 0x2b, 0xde, 0xed, 0x34, 0x1d, 0x44, 0x9e, 0xd2, 0x04, 0x93,
+  0xf4, 0xcb, 0x05, 0x17, 0x2d, 0x09, 0x2d, 0x2d, 0x63, 0xef, 0xf6, 0x26,
+  0x0b, 0x7b,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            18:a2:23:6c:d7:27:c7:52:8d:f6:7b:4b:85:6e:ff:ed
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Jul 29 00:00:00 2010 GMT
+            Not After : Jul 28 23:59:59 2020 GMT
+        Subject: C=US, O=Thawte, Inc., CN=Thawte SGC CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:cd:d9:e9:5c:55:4c:c6:fd:26:0d:3c:9d:56:3a:
+                    7a:46:02:05:eb:f0:c2:ad:be:12:2f:59:ff:67:35:
+                    29:d9:69:d9:4d:37:3e:6d:87:49:bc:bb:d5:16:62:
+                    44:29:71:96:5c:a6:27:e8:c5:9c:fc:19:0b:29:af:
+                    2e:5c:da:0b:8f:bf:ed:53:15:a7:82:35:30:5e:08:
+                    36:32:24:36:36:1a:e4:72:2b:c4:68:48:a4:78:1f:
+                    33:34:20:fe:97:6e:9c:ac:3a:fd:e6:fd:83:5f:75:
+                    83:71:5d:90:df:bd:48:57:6d:10:26:af:6f:41:d8:
+                    cc:78:9e:3d:9c:85:28:89:43:31:ab:a7:6e:a1:bc:
+                    02:e6:be:8f:c3:63:a4:64:68:3b:1b:c3:da:33:c8:
+                    7b:5a:1f:d6:08:72:b2:36:34:18:d3:20:4f:98:e8:
+                    02:93:df:50:b2:67:c8:3d:96:64:55:c7:69:25:0a:
+                    ba:21:36:70:d3:59:a8:82:d2:54:6d:4e:06:5a:e1:
+                    d8:07:8d:35:b8:d0:16:a1:74:fe:4a:1b:70:a8:a9:
+                    43:9a:80:27:a0:40:b7:6f:f9:e3:a8:a8:1e:8a:93:
+                    3c:96:36:a7:88:e9:36:9d:c1:e3:ef:b6:7e:02:37:
+                    62:09:d7:8b:c6:70:d9:32:50:9a:b1:a7:1e:54:21:
+                    1e:49
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.thawte.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3-g5.crl
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-17
+            X509v3 Subject Key Identifier: 
+                24:C0:C0:A4:49:3C:52:0B:12:D8:92:0C:51:D1:87:A7:4D:54:75:2C
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+    Signature Algorithm: sha1WithRSAEncryption
+         38:da:76:35:18:49:32:34:f0:b4:e8:28:08:45:eb:8f:62:3e:
+         99:21:72:77:95:e0:36:82:b3:ff:ab:7f:12:6c:e1:1c:10:c9:
+         54:98:e5:0c:31:74:cc:80:7a:a0:26:a7:45:c8:11:4c:76:e4:
+         d0:a9:b1:c8:92:a3:80:79:26:0d:8d:cf:c8:47:63:2d:13:3c:
+         c2:96:34:d7:00:42:3a:4a:8b:9e:17:a9:dc:c9:50:c5:40:e1:
+         29:45:61:22:f5:b3:b0:88:78:8d:ae:a1:8d:50:6f:44:82:74:
+         52:87:15:0c:1c:4e:f2:16:37:da:c1:05:69:d9:01:54:ee:cd:
+         71:49:f6:6c:56:7c:75:73:e2:8a:9f:a6:69:d7:60:9f:04:c3:
+         a3:9f:81:60:b3:c5:bd:a5:55:d0:69:db:45:98:64:20:f2:c0:
+         8b:8c:4e:e9:57:52:36:ab:bb:53:67:30:89:63:13:28:f3:44:
+         d1:43:76:b4:81:68:2a:07:21:3f:8f:f4:67:d3:08:a0:79:de:
+         cc:b9:53:2d:1f:44:d3:54:9c:a3:07:4d:8a:08:34:4d:dd:17:
+         7a:fe:ad:6b:4b:99:b6:00:c9:62:76:7e:98:9a:a2:49:1c:86:
+         be:b2:55:95:2c:2d:27:21:bc:19:b0:f1:3e:ad:b6:d1:1a:de:
+         ed:b6:ee:35
+-----BEGIN CERTIFICATE-----
+MIIEyzCCA7OgAwIBAgIQGKIjbNcnx1KN9ntLhW7/7TANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwNzI5MDAwMDAwWhcNMjAwNzI4MjM1OTU5WjBBMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRswGQYDVQQDExJUaGF3
+dGUgU0dDIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDN
+2elcVUzG/SYNPJ1WOnpGAgXr8MKtvhIvWf9nNSnZadlNNz5th0m8u9UWYkQpcZZc
+pifoxZz8GQspry5c2guPv+1TFaeCNTBeCDYyJDY2GuRyK8RoSKR4HzM0IP6Xbpys
+Ov3m/YNfdYNxXZDfvUhXbRAmr29B2Mx4nj2chSiJQzGrp26hvALmvo/DY6RkaDsb
+w9ozyHtaH9YIcrI2NBjTIE+Y6AKT31CyZ8g9lmRVx2klCrohNnDTWaiC0lRtTgZa
+4dgHjTW40BahdP5KG3CoqUOagCegQLdv+eOoqB6KkzyWNqeI6TadwePvtn4CN2IJ
+14vGcNkyUJqxpx5UIR5JAgMBAAGjggEzMIIBLzAyBggrBgEFBQcBAQQmMCQwIgYI
+KwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgwBgEB
+/wIBADA0BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vY3JsLnZlcmlzaWduLmNvbS9w
+Y2EzLWc1LmNybDA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG
++EIEAQYKYIZIAYb4RQEIATAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwx
+GjAYBgNVBAMTEVZlcmlTaWduTVBLSS0yLTE3MB0GA1UdDgQWBBQkwMCkSTxSCxLY
+kgxR0YenTVR1LDAfBgNVHSMEGDAWgBR/02Wnwt3su/AwCfNDOfoCrzMxMzANBgkq
+hkiG9w0BAQUFAAOCAQEAONp2NRhJMjTwtOgoCEXrj2I+mSFyd5XgNoKz/6t/Emzh
+HBDJVJjlDDF0zIB6oCanRcgRTHbk0KmxyJKjgHkmDY3PyEdjLRM8wpY01wBCOkqL
+nhep3MlQxUDhKUVhIvWzsIh4ja6hjVBvRIJ0UocVDBxO8hY32sEFadkBVO7NcUn2
+bFZ8dXPiip+maddgnwTDo5+BYLPFvaVV0GnbRZhkIPLAi4xO6VdSNqu7U2cwiWMT
+KPNE0UN2tIFoKgchP4/0Z9MIoHnezLlTLR9E01ScowdNigg0Td0Xev6ta0uZtgDJ
+YnZ+mJqiSRyGvrJVlSwtJyG8GbDxPq220Rre7bbuNQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert53[] = {
+  0x30, 0x82, 0x04, 0xcb, 0x30, 0x82, 0x03, 0xb3, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x18, 0xa2, 0x23, 0x6c, 0xd7, 0x27, 0xc7, 0x52, 0x8d,
+  0xf6, 0x7b, 0x4b, 0x85, 0x6e, 0xff, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x37, 0x32, 0x39, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x37, 0x32, 0x38,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x41, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15,
+  0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x54, 0x68, 0x61,
+  0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30,
+  0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x54, 0x68, 0x61, 0x77,
+  0x74, 0x65, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+  0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+  0xd9, 0xe9, 0x5c, 0x55, 0x4c, 0xc6, 0xfd, 0x26, 0x0d, 0x3c, 0x9d, 0x56,
+  0x3a, 0x7a, 0x46, 0x02, 0x05, 0xeb, 0xf0, 0xc2, 0xad, 0xbe, 0x12, 0x2f,
+  0x59, 0xff, 0x67, 0x35, 0x29, 0xd9, 0x69, 0xd9, 0x4d, 0x37, 0x3e, 0x6d,
+  0x87, 0x49, 0xbc, 0xbb, 0xd5, 0x16, 0x62, 0x44, 0x29, 0x71, 0x96, 0x5c,
+  0xa6, 0x27, 0xe8, 0xc5, 0x9c, 0xfc, 0x19, 0x0b, 0x29, 0xaf, 0x2e, 0x5c,
+  0xda, 0x0b, 0x8f, 0xbf, 0xed, 0x53, 0x15, 0xa7, 0x82, 0x35, 0x30, 0x5e,
+  0x08, 0x36, 0x32, 0x24, 0x36, 0x36, 0x1a, 0xe4, 0x72, 0x2b, 0xc4, 0x68,
+  0x48, 0xa4, 0x78, 0x1f, 0x33, 0x34, 0x20, 0xfe, 0x97, 0x6e, 0x9c, 0xac,
+  0x3a, 0xfd, 0xe6, 0xfd, 0x83, 0x5f, 0x75, 0x83, 0x71, 0x5d, 0x90, 0xdf,
+  0xbd, 0x48, 0x57, 0x6d, 0x10, 0x26, 0xaf, 0x6f, 0x41, 0xd8, 0xcc, 0x78,
+  0x9e, 0x3d, 0x9c, 0x85, 0x28, 0x89, 0x43, 0x31, 0xab, 0xa7, 0x6e, 0xa1,
+  0xbc, 0x02, 0xe6, 0xbe, 0x8f, 0xc3, 0x63, 0xa4, 0x64, 0x68, 0x3b, 0x1b,
+  0xc3, 0xda, 0x33, 0xc8, 0x7b, 0x5a, 0x1f, 0xd6, 0x08, 0x72, 0xb2, 0x36,
+  0x34, 0x18, 0xd3, 0x20, 0x4f, 0x98, 0xe8, 0x02, 0x93, 0xdf, 0x50, 0xb2,
+  0x67, 0xc8, 0x3d, 0x96, 0x64, 0x55, 0xc7, 0x69, 0x25, 0x0a, 0xba, 0x21,
+  0x36, 0x70, 0xd3, 0x59, 0xa8, 0x82, 0xd2, 0x54, 0x6d, 0x4e, 0x06, 0x5a,
+  0xe1, 0xd8, 0x07, 0x8d, 0x35, 0xb8, 0xd0, 0x16, 0xa1, 0x74, 0xfe, 0x4a,
+  0x1b, 0x70, 0xa8, 0xa9, 0x43, 0x9a, 0x80, 0x27, 0xa0, 0x40, 0xb7, 0x6f,
+  0xf9, 0xe3, 0xa8, 0xa8, 0x1e, 0x8a, 0x93, 0x3c, 0x96, 0x36, 0xa7, 0x88,
+  0xe9, 0x36, 0x9d, 0xc1, 0xe3, 0xef, 0xb6, 0x7e, 0x02, 0x37, 0x62, 0x09,
+  0xd7, 0x8b, 0xc6, 0x70, 0xd9, 0x32, 0x50, 0x9a, 0xb1, 0xa7, 0x1e, 0x54,
+  0x21, 0x1e, 0x49, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x33,
+  0x30, 0x82, 0x01, 0x2f, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68,
+  0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+  0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65,
+  0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+  0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34,
+  0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86,
+  0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+  0x45, 0x01, 0x08, 0x01, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03,
+  0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31,
+  0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32,
+  0x2d, 0x31, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0x24, 0xc0, 0xc0, 0xa4, 0x49, 0x3c, 0x52, 0x0b, 0x12, 0xd8,
+  0x92, 0x0c, 0x51, 0xd1, 0x87, 0xa7, 0x4d, 0x54, 0x75, 0x2c, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f,
+  0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43,
+  0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x38, 0xda, 0x76, 0x35, 0x18, 0x49, 0x32, 0x34, 0xf0,
+  0xb4, 0xe8, 0x28, 0x08, 0x45, 0xeb, 0x8f, 0x62, 0x3e, 0x99, 0x21, 0x72,
+  0x77, 0x95, 0xe0, 0x36, 0x82, 0xb3, 0xff, 0xab, 0x7f, 0x12, 0x6c, 0xe1,
+  0x1c, 0x10, 0xc9, 0x54, 0x98, 0xe5, 0x0c, 0x31, 0x74, 0xcc, 0x80, 0x7a,
+  0xa0, 0x26, 0xa7, 0x45, 0xc8, 0x11, 0x4c, 0x76, 0xe4, 0xd0, 0xa9, 0xb1,
+  0xc8, 0x92, 0xa3, 0x80, 0x79, 0x26, 0x0d, 0x8d, 0xcf, 0xc8, 0x47, 0x63,
+  0x2d, 0x13, 0x3c, 0xc2, 0x96, 0x34, 0xd7, 0x00, 0x42, 0x3a, 0x4a, 0x8b,
+  0x9e, 0x17, 0xa9, 0xdc, 0xc9, 0x50, 0xc5, 0x40, 0xe1, 0x29, 0x45, 0x61,
+  0x22, 0xf5, 0xb3, 0xb0, 0x88, 0x78, 0x8d, 0xae, 0xa1, 0x8d, 0x50, 0x6f,
+  0x44, 0x82, 0x74, 0x52, 0x87, 0x15, 0x0c, 0x1c, 0x4e, 0xf2, 0x16, 0x37,
+  0xda, 0xc1, 0x05, 0x69, 0xd9, 0x01, 0x54, 0xee, 0xcd, 0x71, 0x49, 0xf6,
+  0x6c, 0x56, 0x7c, 0x75, 0x73, 0xe2, 0x8a, 0x9f, 0xa6, 0x69, 0xd7, 0x60,
+  0x9f, 0x04, 0xc3, 0xa3, 0x9f, 0x81, 0x60, 0xb3, 0xc5, 0xbd, 0xa5, 0x55,
+  0xd0, 0x69, 0xdb, 0x45, 0x98, 0x64, 0x20, 0xf2, 0xc0, 0x8b, 0x8c, 0x4e,
+  0xe9, 0x57, 0x52, 0x36, 0xab, 0xbb, 0x53, 0x67, 0x30, 0x89, 0x63, 0x13,
+  0x28, 0xf3, 0x44, 0xd1, 0x43, 0x76, 0xb4, 0x81, 0x68, 0x2a, 0x07, 0x21,
+  0x3f, 0x8f, 0xf4, 0x67, 0xd3, 0x08, 0xa0, 0x79, 0xde, 0xcc, 0xb9, 0x53,
+  0x2d, 0x1f, 0x44, 0xd3, 0x54, 0x9c, 0xa3, 0x07, 0x4d, 0x8a, 0x08, 0x34,
+  0x4d, 0xdd, 0x17, 0x7a, 0xfe, 0xad, 0x6b, 0x4b, 0x99, 0xb6, 0x00, 0xc9,
+  0x62, 0x76, 0x7e, 0x98, 0x9a, 0xa2, 0x49, 0x1c, 0x86, 0xbe, 0xb2, 0x55,
+  0x95, 0x2c, 0x2d, 0x27, 0x21, 0xbc, 0x19, 0xb0, 0xf1, 0x3e, 0xad, 0xb6,
+  0xd1, 0x1a, 0xde, 0xed, 0xb6, 0xee, 0x35,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            25:0c:e8:e0:30:61:2e:9f:2b:89:f7:05:4d:7c:f8:fd
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2021 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+                    4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+                    08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+                    2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+                    8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+                    a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+                    54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+                    d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+                    7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+                    bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+                    f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+                    ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+                    f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+                    21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+                    63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+                    ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+                    9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+                    25:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 Subject Key Identifier: 
+                7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+    Signature Algorithm: sha1WithRSAEncryption
+         13:02:dd:f8:e8:86:00:f2:5a:f8:f8:20:0c:59:88:62:07:ce:
+         ce:f7:4e:f9:bb:59:a1:98:e5:e1:38:dd:4e:bc:66:18:d3:ad:
+         eb:18:f2:0d:c9:6d:3e:4a:94:20:c3:3c:ba:bd:65:54:c6:af:
+         44:b3:10:ad:2c:6b:3e:ab:d7:07:b6:b8:81:63:c5:f9:5e:2e:
+         e5:2a:67:ce:cd:33:0c:2a:d7:89:56:03:23:1f:b3:be:e8:3a:
+         08:59:b4:ec:45:35:f7:8a:5b:ff:66:cf:50:af:c6:6d:57:8d:
+         19:78:b7:b9:a2:d1:57:ea:1f:9a:4b:af:ba:c9:8e:12:7e:c6:
+         bd:ff
+-----BEGIN CERTIFICATE-----
+MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
+KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
+j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
+L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
+b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC
+BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA
+A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K
+lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ
+tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert54[] = {
+  0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x04, 0x39, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x25, 0x0c, 0xe8, 0xe0, 0x30, 0x61, 0x2e, 0x9f, 0x2b,
+  0x89, 0xf7, 0x05, 0x4d, 0x7c, 0xf8, 0xfd, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+  0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+  0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+  0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+  0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+  0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+  0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+  0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+  0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+  0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+  0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+  0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+  0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+  0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+  0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+  0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+  0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+  0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+  0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+  0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+  0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+  0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+  0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+  0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+  0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+  0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+  0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+  0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+  0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+  0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x9b, 0x30, 0x82, 0x01, 0x97, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+  0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+  0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+  0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+  0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+  0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f,
+  0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09,
+  0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30,
+  0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14,
+  0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80,
+  0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30,
+  0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x25,
+  0x04, 0x37, 0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x09,
+  0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60,
+  0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x81, 0x81, 0x00, 0x13, 0x02, 0xdd, 0xf8, 0xe8, 0x86, 0x00, 0xf2,
+  0x5a, 0xf8, 0xf8, 0x20, 0x0c, 0x59, 0x88, 0x62, 0x07, 0xce, 0xce, 0xf7,
+  0x4e, 0xf9, 0xbb, 0x59, 0xa1, 0x98, 0xe5, 0xe1, 0x38, 0xdd, 0x4e, 0xbc,
+  0x66, 0x18, 0xd3, 0xad, 0xeb, 0x18, 0xf2, 0x0d, 0xc9, 0x6d, 0x3e, 0x4a,
+  0x94, 0x20, 0xc3, 0x3c, 0xba, 0xbd, 0x65, 0x54, 0xc6, 0xaf, 0x44, 0xb3,
+  0x10, 0xad, 0x2c, 0x6b, 0x3e, 0xab, 0xd7, 0x07, 0xb6, 0xb8, 0x81, 0x63,
+  0xc5, 0xf9, 0x5e, 0x2e, 0xe5, 0x2a, 0x67, 0xce, 0xcd, 0x33, 0x0c, 0x2a,
+  0xd7, 0x89, 0x56, 0x03, 0x23, 0x1f, 0xb3, 0xbe, 0xe8, 0x3a, 0x08, 0x59,
+  0xb4, 0xec, 0x45, 0x35, 0xf7, 0x8a, 0x5b, 0xff, 0x66, 0xcf, 0x50, 0xaf,
+  0xc6, 0x6d, 0x57, 0x8d, 0x19, 0x78, 0xb7, 0xb9, 0xa2, 0xd1, 0x57, 0xea,
+  0x1f, 0x9a, 0x4b, 0xaf, 0xba, 0xc9, 0x8e, 0x12, 0x7e, 0xc6, 0xbd, 0xff,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            48:fc:4b:0a:37:06:ff:46:fe:d3:de:5d:4c:1e:ca:62
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Nov 26 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions DV Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a7:57:66:35:dc:90:4e:78:74:65:a8:da:ac:e8:
+                    e9:62:65:46:ca:64:f0:d0:c3:a7:42:5b:8c:e1:8e:
+                    00:05:2b:53:e4:1b:84:22:b4:df:57:b0:40:8f:17:
+                    92:7e:31:97:1e:f5:ad:f0:80:99:db:97:30:95:35:
+                    0d:64:41:df:c4:b3:82:79:cd:bc:96:e2:fd:00:29:
+                    c5:3e:be:7c:08:6c:be:fe:90:b1:15:39:21:86:34:
+                    40:bd:9c:9d:fa:6a:e5:2a:68:45:0e:68:e0:e8:b0:
+                    08:65:84:36:31:9c:46:e1:4e:cb:3f:58:83:f3:6c:
+                    8e:34:19:82:53:26:2c:8d:ab:92:22:5f:05:a1:3d:
+                    9b:ae:67:b4:56:c0:f9:97:78:c0:b5:98:15:0c:ad:
+                    03:ad:ff:78:8f:2f:26:7c:3a:dc:94:00:87:c3:7e:
+                    c2:b6:a8:8c:0b:1d:1d:0f:8c:b5:d0:fb:93:3a:38:
+                    f6:08:fe:3b:8d:66:6b:45:c6:5f:b2:7b:f0:14:f9:
+                    81:75:de:0b:4b:83:cb:ee:77:bb:9c:7e:9b:9d:27:
+                    d8:90:06:9d:cf:4b:3c:2b:fa:bf:01:0a:c5:6d:1c:
+                    5a:60:68:92:f9:0e:43:fb:f2:88:78:96:e5:53:4b:
+                    51:f6:b1:e7:6d:f7:c6:ff:4f:d7:03:7b:73:f2:60:
+                    0a:21
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                58:D8:25:92:A4:55:5A:6E:D9:A3:D1:A3:7C:0C:AA:04:21:71:2E:60
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.782.1.2.1.9.1
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         27:94:9f:7e:7f:fc:73:48:f2:38:1f:b0:05:bf:71:dc:3b:a9:
+         7e:c3:10:86:25:2d:14:ee:44:9d:4a:8d:f2:b3:3a:c5:66:fa:
+         02:bf:d5:f0:00:16:77:c9:74:d7:88:c0:b1:18:7a:f3:4e:13:
+         31:70:6f:46:70:41:e1:1a:42:3e:aa:5f:46:18:2d:85:0c:3b:
+         bb:fe:cf:02:d6:cf:ae:db:1a:93:52:74:6c:9e:fa:b2:ee:af:
+         2f:7d:07:42:17:7d:31:e5:6a:36:28:2b:fd:d4:72:f1:fe:b9:
+         c5:f7:f0:72:61:e0:9d:bc:ca:eb:45:0b:b8:68:09:01:1b:4d:
+         73:7f:df:e6:93:ba:1d:fc:6b:28:b3:64:30:bb:d0:3a:aa:35:
+         6b:0b:83:61:68:d7:32:5a:49:de:1a:d1:fc:6d:8b:a0:dc:fa:
+         7a:a4:92:7f:74:e2:0d:92:a0:9e:b8:46:1c:62:63:b0:b8:08:
+         c4:fd:b0:b4:9f:24:09:b3:2d:9c:75:14:77:4a:6e:c4:63:c1:
+         4d:13:86:ce:98:72:1d:3d:b9:c6:4e:73:30:e4:c6:73:a2:d1:
+         f7:90:e4:90:cc:e1:3a:37:d6:53:02:5f:45:2d:2f:a6:4f:49:
+         41:ea:df:8f:2f:97:1c:76:db:78:40:63:cb:e4:d5:d7:53:38:
+         0e:11:10:38
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQSPxLCjcG/0b+095dTB7KYjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMTEyNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+WTELMAkGA1UEBhMCVVMxITAfBgNVBAoTGE5ldHdvcmsgU29sdXRpb25zIEwuTC5D
+LjEnMCUGA1UEAxMeTmV0d29yayBTb2x1dGlvbnMgRFYgU2VydmVyIENBMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp1dmNdyQTnh0ZajarOjpYmVGymTw
+0MOnQluM4Y4ABStT5BuEIrTfV7BAjxeSfjGXHvWt8ICZ25cwlTUNZEHfxLOCec28
+luL9ACnFPr58CGy+/pCxFTkhhjRAvZyd+mrlKmhFDmjg6LAIZYQ2MZxG4U7LP1iD
+82yONBmCUyYsjauSIl8FoT2brme0VsD5l3jAtZgVDK0Drf94jy8mfDrclACHw37C
+tqiMCx0dD4y10PuTOjj2CP47jWZrRcZfsnvwFPmBdd4LS4PL7ne7nH6bnSfYkAad
+z0s8K/q/AQrFbRxaYGiS+Q5D+/KIeJblU0tR9rHnbffG/0/XA3tz8mAKIQIDAQAB
+o4IBfzCCAXswHwYDVR0jBBgwFoAUrb2YejS0Jvf6xCZU7wO94CTLVBowHQYDVR0O
+BBYEFFjYJZKkVVpu2aPRo3wMqgQhcS5gMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMB
+Af8ECDAGAQH/AgEAMBkGA1UdIAQSMBAwDgYMKwYBBAGGDgECAQkBMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0
+dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3
+YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAnlJ9+f/xzSPI4H7AFv3HcO6l+wxCGJS0U
+7kSdSo3yszrFZvoCv9XwABZ3yXTXiMCxGHrzThMxcG9GcEHhGkI+ql9GGC2FDDu7
+/s8C1s+u2xqTUnRsnvqy7q8vfQdCF30x5Wo2KCv91HLx/rnF9/ByYeCdvMrrRQu4
+aAkBG01zf9/mk7od/Gsos2Qwu9A6qjVrC4NhaNcyWkneGtH8bYug3Pp6pJJ/dOIN
+kqCeuEYcYmOwuAjE/bC0nyQJsy2cdRR3Sm7EY8FNE4bOmHIdPbnGTnMw5MZzotH3
+kOSQzOE6N9ZTAl9FLS+mT0lB6t+PL5ccdtt4QGPL5NXXUzgOERA4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert55[] = {
+  0x30, 0x82, 0x04, 0xd3, 0x30, 0x82, 0x03, 0xbb, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x48, 0xfc, 0x4b, 0x0a, 0x37, 0x06, 0xff, 0x46, 0xfe,
+  0xd3, 0xde, 0x5d, 0x4c, 0x1e, 0xca, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x31, 0x32,
+  0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x59, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c,
+  0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43,
+  0x2e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e,
+  0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+  0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x44, 0x56, 0x20, 0x53, 0x65, 0x72,
+  0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+  0x01, 0x01, 0x00, 0xa7, 0x57, 0x66, 0x35, 0xdc, 0x90, 0x4e, 0x78, 0x74,
+  0x65, 0xa8, 0xda, 0xac, 0xe8, 0xe9, 0x62, 0x65, 0x46, 0xca, 0x64, 0xf0,
+  0xd0, 0xc3, 0xa7, 0x42, 0x5b, 0x8c, 0xe1, 0x8e, 0x00, 0x05, 0x2b, 0x53,
+  0xe4, 0x1b, 0x84, 0x22, 0xb4, 0xdf, 0x57, 0xb0, 0x40, 0x8f, 0x17, 0x92,
+  0x7e, 0x31, 0x97, 0x1e, 0xf5, 0xad, 0xf0, 0x80, 0x99, 0xdb, 0x97, 0x30,
+  0x95, 0x35, 0x0d, 0x64, 0x41, 0xdf, 0xc4, 0xb3, 0x82, 0x79, 0xcd, 0xbc,
+  0x96, 0xe2, 0xfd, 0x00, 0x29, 0xc5, 0x3e, 0xbe, 0x7c, 0x08, 0x6c, 0xbe,
+  0xfe, 0x90, 0xb1, 0x15, 0x39, 0x21, 0x86, 0x34, 0x40, 0xbd, 0x9c, 0x9d,
+  0xfa, 0x6a, 0xe5, 0x2a, 0x68, 0x45, 0x0e, 0x68, 0xe0, 0xe8, 0xb0, 0x08,
+  0x65, 0x84, 0x36, 0x31, 0x9c, 0x46, 0xe1, 0x4e, 0xcb, 0x3f, 0x58, 0x83,
+  0xf3, 0x6c, 0x8e, 0x34, 0x19, 0x82, 0x53, 0x26, 0x2c, 0x8d, 0xab, 0x92,
+  0x22, 0x5f, 0x05, 0xa1, 0x3d, 0x9b, 0xae, 0x67, 0xb4, 0x56, 0xc0, 0xf9,
+  0x97, 0x78, 0xc0, 0xb5, 0x98, 0x15, 0x0c, 0xad, 0x03, 0xad, 0xff, 0x78,
+  0x8f, 0x2f, 0x26, 0x7c, 0x3a, 0xdc, 0x94, 0x00, 0x87, 0xc3, 0x7e, 0xc2,
+  0xb6, 0xa8, 0x8c, 0x0b, 0x1d, 0x1d, 0x0f, 0x8c, 0xb5, 0xd0, 0xfb, 0x93,
+  0x3a, 0x38, 0xf6, 0x08, 0xfe, 0x3b, 0x8d, 0x66, 0x6b, 0x45, 0xc6, 0x5f,
+  0xb2, 0x7b, 0xf0, 0x14, 0xf9, 0x81, 0x75, 0xde, 0x0b, 0x4b, 0x83, 0xcb,
+  0xee, 0x77, 0xbb, 0x9c, 0x7e, 0x9b, 0x9d, 0x27, 0xd8, 0x90, 0x06, 0x9d,
+  0xcf, 0x4b, 0x3c, 0x2b, 0xfa, 0xbf, 0x01, 0x0a, 0xc5, 0x6d, 0x1c, 0x5a,
+  0x60, 0x68, 0x92, 0xf9, 0x0e, 0x43, 0xfb, 0xf2, 0x88, 0x78, 0x96, 0xe5,
+  0x53, 0x4b, 0x51, 0xf6, 0xb1, 0xe7, 0x6d, 0xf7, 0xc6, 0xff, 0x4f, 0xd7,
+  0x03, 0x7b, 0x73, 0xf2, 0x60, 0x0a, 0x21, 0x02, 0x03, 0x01, 0x00, 0x01,
+  0xa3, 0x82, 0x01, 0x7f, 0x30, 0x82, 0x01, 0x7b, 0x30, 0x1f, 0x06, 0x03,
+  0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98,
+  0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd,
+  0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0x58, 0xd8, 0x25, 0x92, 0xa4, 0x55, 0x5a, 0x6e,
+  0xd9, 0xa3, 0xd1, 0xa3, 0x7c, 0x0c, 0xaa, 0x04, 0x21, 0x71, 0x2e, 0x60,
+  0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+  0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+  0x30, 0x19, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30, 0x10, 0x30,
+  0x0e, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x86, 0x0e, 0x01, 0x02,
+  0x01, 0x09, 0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d,
+  0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+  0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+  0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37,
+  0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+  0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x27,
+  0x94, 0x9f, 0x7e, 0x7f, 0xfc, 0x73, 0x48, 0xf2, 0x38, 0x1f, 0xb0, 0x05,
+  0xbf, 0x71, 0xdc, 0x3b, 0xa9, 0x7e, 0xc3, 0x10, 0x86, 0x25, 0x2d, 0x14,
+  0xee, 0x44, 0x9d, 0x4a, 0x8d, 0xf2, 0xb3, 0x3a, 0xc5, 0x66, 0xfa, 0x02,
+  0xbf, 0xd5, 0xf0, 0x00, 0x16, 0x77, 0xc9, 0x74, 0xd7, 0x88, 0xc0, 0xb1,
+  0x18, 0x7a, 0xf3, 0x4e, 0x13, 0x31, 0x70, 0x6f, 0x46, 0x70, 0x41, 0xe1,
+  0x1a, 0x42, 0x3e, 0xaa, 0x5f, 0x46, 0x18, 0x2d, 0x85, 0x0c, 0x3b, 0xbb,
+  0xfe, 0xcf, 0x02, 0xd6, 0xcf, 0xae, 0xdb, 0x1a, 0x93, 0x52, 0x74, 0x6c,
+  0x9e, 0xfa, 0xb2, 0xee, 0xaf, 0x2f, 0x7d, 0x07, 0x42, 0x17, 0x7d, 0x31,
+  0xe5, 0x6a, 0x36, 0x28, 0x2b, 0xfd, 0xd4, 0x72, 0xf1, 0xfe, 0xb9, 0xc5,
+  0xf7, 0xf0, 0x72, 0x61, 0xe0, 0x9d, 0xbc, 0xca, 0xeb, 0x45, 0x0b, 0xb8,
+  0x68, 0x09, 0x01, 0x1b, 0x4d, 0x73, 0x7f, 0xdf, 0xe6, 0x93, 0xba, 0x1d,
+  0xfc, 0x6b, 0x28, 0xb3, 0x64, 0x30, 0xbb, 0xd0, 0x3a, 0xaa, 0x35, 0x6b,
+  0x0b, 0x83, 0x61, 0x68, 0xd7, 0x32, 0x5a, 0x49, 0xde, 0x1a, 0xd1, 0xfc,
+  0x6d, 0x8b, 0xa0, 0xdc, 0xfa, 0x7a, 0xa4, 0x92, 0x7f, 0x74, 0xe2, 0x0d,
+  0x92, 0xa0, 0x9e, 0xb8, 0x46, 0x1c, 0x62, 0x63, 0xb0, 0xb8, 0x08, 0xc4,
+  0xfd, 0xb0, 0xb4, 0x9f, 0x24, 0x09, 0xb3, 0x2d, 0x9c, 0x75, 0x14, 0x77,
+  0x4a, 0x6e, 0xc4, 0x63, 0xc1, 0x4d, 0x13, 0x86, 0xce, 0x98, 0x72, 0x1d,
+  0x3d, 0xb9, 0xc6, 0x4e, 0x73, 0x30, 0xe4, 0xc6, 0x73, 0xa2, 0xd1, 0xf7,
+  0x90, 0xe4, 0x90, 0xcc, 0xe1, 0x3a, 0x37, 0xd6, 0x53, 0x02, 0x5f, 0x45,
+  0x2d, 0x2f, 0xa6, 0x4f, 0x49, 0x41, 0xea, 0xdf, 0x8f, 0x2f, 0x97, 0x1c,
+  0x76, 0xdb, 0x78, 0x40, 0x63, 0xcb, 0xe4, 0xd5, 0xd7, 0x53, 0x38, 0x0e,
+  0x11, 0x10, 0x38,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 769 (0x301)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
+        Validity
+            Not Before: Nov 16 01:54:37 2006 GMT
+            Not After : Nov 16 01:54:37 2026 GMT
+        Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c4:2d:d5:15:8c:9c:26:4c:ec:32:35:eb:5f:b8:
+                    59:01:5a:a6:61:81:59:3b:70:63:ab:e3:dc:3d:c7:
+                    2a:b8:c9:33:d3:79:e4:3a:ed:3c:30:23:84:8e:b3:
+                    30:14:b6:b2:87:c3:3d:95:54:04:9e:df:99:dd:0b:
+                    25:1e:21:de:65:29:7e:35:a8:a9:54:eb:f6:f7:32:
+                    39:d4:26:55:95:ad:ef:fb:fe:58:86:d7:9e:f4:00:
+                    8d:8c:2a:0c:bd:42:04:ce:a7:3f:04:f6:ee:80:f2:
+                    aa:ef:52:a1:69:66:da:be:1a:ad:5d:da:2c:66:ea:
+                    1a:6b:bb:e5:1a:51:4a:00:2f:48:c7:98:75:d8:b9:
+                    29:c8:ee:f8:66:6d:0a:9c:b3:f3:fc:78:7c:a2:f8:
+                    a3:f2:b5:c3:f3:b9:7a:91:c1:a7:e6:25:2e:9c:a8:
+                    ed:12:65:6e:6a:f6:12:44:53:70:30:95:c3:9c:2b:
+                    58:2b:3d:08:74:4a:f2:be:51:b0:bf:87:d0:4c:27:
+                    58:6b:b5:35:c5:9d:af:17:31:f8:0b:8f:ee:ad:81:
+                    36:05:89:08:98:cf:3a:af:25:87:c0:49:ea:a7:fd:
+                    67:f7:45:8e:97:cc:14:39:e2:36:85:b5:7e:1a:37:
+                    fd:16:f6:71:11:9a:74:30:16:fe:13:94:a3:3f:84:
+                    0d:4f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                FD:AC:61:32:93:6C:45:D6:E2:EE:85:5F:9A:BA:E7:76:99:68:CC:E7
+            X509v3 Authority Key Identifier: 
+                keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.godaddy.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://certificates.godaddy.com/repository/gdroot.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://certificates.godaddy.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+    Signature Algorithm: sha1WithRSAEncryption
+         d2:86:c0:ec:bd:f9:a1:b6:67:ee:66:0b:a2:06:3a:04:50:8e:
+         15:72:ac:4a:74:95:53:cb:37:cb:44:49:ef:07:90:6b:33:d9:
+         96:f0:94:56:a5:13:30:05:3c:85:32:21:7b:c9:c7:0a:a8:24:
+         a4:90:de:46:d3:25:23:14:03:67:c2:10:d6:6f:0f:5d:7b:7a:
+         cc:9f:c5:58:2a:c1:c4:9e:21:a8:5a:f3:ac:a4:46:f3:9e:e4:
+         63:cb:2f:90:a4:29:29:01:d9:72:2c:29:df:37:01:27:bc:4f:
+         ee:68:d3:21:8f:c0:b3:e4:f5:09:ed:d2:10:aa:53:b4:be:f0:
+         cc:59:0b:d6:3b:96:1c:95:24:49:df:ce:ec:fd:a7:48:91:14:
+         45:0e:3a:36:6f:da:45:b3:45:a2:41:c9:d4:d7:44:4e:3e:b9:
+         74:76:d5:a2:13:55:2c:c6:87:a3:b5:99:ac:06:84:87:7f:75:
+         06:fc:bf:14:4c:0e:cc:6e:c4:df:3d:b7:12:71:f4:e8:f1:51:
+         40:22:28:49:e0:1d:4b:87:a8:34:cc:06:a2:dd:12:5a:d1:86:
+         36:64:03:35:6f:6f:77:6e:eb:f2:85:50:98:5e:ab:03:53:ad:
+         91:23:63:1f:16:9c:cd:b9:b2:05:63:3a:e1:f4:68:1b:17:05:
+         35:95:53:ee
+-----BEGIN CERTIFICATE-----
+MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
+ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
+MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
+QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
+b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
+b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
+KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
+VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
+SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
+cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
+6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
+MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
+kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
+BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
+c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
+AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
+BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
+OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
+A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
+0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
+RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
+qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
+U+4=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert56[] = {
+  0x30, 0x82, 0x04, 0xde, 0x30, 0x82, 0x03, 0xc6, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x02, 0x03, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x63, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x54, 0x68,
+  0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x47,
+  0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31,
+  0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x47, 0x6f, 0x20,
+  0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x36, 0x30,
+  0x31, 0x35, 0x34, 0x33, 0x37, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x31,
+  0x31, 0x36, 0x30, 0x31, 0x35, 0x34, 0x33, 0x37, 0x5a, 0x30, 0x81, 0xca,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07,
+  0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73,
+  0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+  0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+  0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27,
+  0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x05,
+  0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37, 0x30, 0x82,
+  0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+  0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0x2d, 0xd5, 0x15, 0x8c,
+  0x9c, 0x26, 0x4c, 0xec, 0x32, 0x35, 0xeb, 0x5f, 0xb8, 0x59, 0x01, 0x5a,
+  0xa6, 0x61, 0x81, 0x59, 0x3b, 0x70, 0x63, 0xab, 0xe3, 0xdc, 0x3d, 0xc7,
+  0x2a, 0xb8, 0xc9, 0x33, 0xd3, 0x79, 0xe4, 0x3a, 0xed, 0x3c, 0x30, 0x23,
+  0x84, 0x8e, 0xb3, 0x30, 0x14, 0xb6, 0xb2, 0x87, 0xc3, 0x3d, 0x95, 0x54,
+  0x04, 0x9e, 0xdf, 0x99, 0xdd, 0x0b, 0x25, 0x1e, 0x21, 0xde, 0x65, 0x29,
+  0x7e, 0x35, 0xa8, 0xa9, 0x54, 0xeb, 0xf6, 0xf7, 0x32, 0x39, 0xd4, 0x26,
+  0x55, 0x95, 0xad, 0xef, 0xfb, 0xfe, 0x58, 0x86, 0xd7, 0x9e, 0xf4, 0x00,
+  0x8d, 0x8c, 0x2a, 0x0c, 0xbd, 0x42, 0x04, 0xce, 0xa7, 0x3f, 0x04, 0xf6,
+  0xee, 0x80, 0xf2, 0xaa, 0xef, 0x52, 0xa1, 0x69, 0x66, 0xda, 0xbe, 0x1a,
+  0xad, 0x5d, 0xda, 0x2c, 0x66, 0xea, 0x1a, 0x6b, 0xbb, 0xe5, 0x1a, 0x51,
+  0x4a, 0x00, 0x2f, 0x48, 0xc7, 0x98, 0x75, 0xd8, 0xb9, 0x29, 0xc8, 0xee,
+  0xf8, 0x66, 0x6d, 0x0a, 0x9c, 0xb3, 0xf3, 0xfc, 0x78, 0x7c, 0xa2, 0xf8,
+  0xa3, 0xf2, 0xb5, 0xc3, 0xf3, 0xb9, 0x7a, 0x91, 0xc1, 0xa7, 0xe6, 0x25,
+  0x2e, 0x9c, 0xa8, 0xed, 0x12, 0x65, 0x6e, 0x6a, 0xf6, 0x12, 0x44, 0x53,
+  0x70, 0x30, 0x95, 0xc3, 0x9c, 0x2b, 0x58, 0x2b, 0x3d, 0x08, 0x74, 0x4a,
+  0xf2, 0xbe, 0x51, 0xb0, 0xbf, 0x87, 0xd0, 0x4c, 0x27, 0x58, 0x6b, 0xb5,
+  0x35, 0xc5, 0x9d, 0xaf, 0x17, 0x31, 0xf8, 0x0b, 0x8f, 0xee, 0xad, 0x81,
+  0x36, 0x05, 0x89, 0x08, 0x98, 0xcf, 0x3a, 0xaf, 0x25, 0x87, 0xc0, 0x49,
+  0xea, 0xa7, 0xfd, 0x67, 0xf7, 0x45, 0x8e, 0x97, 0xcc, 0x14, 0x39, 0xe2,
+  0x36, 0x85, 0xb5, 0x7e, 0x1a, 0x37, 0xfd, 0x16, 0xf6, 0x71, 0x11, 0x9a,
+  0x74, 0x30, 0x16, 0xfe, 0x13, 0x94, 0xa3, 0x3f, 0x84, 0x0d, 0x4f, 0x02,
+  0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x32, 0x30, 0x82, 0x01, 0x2e,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xfd,
+  0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, 0x85, 0x5f, 0x9a,
+  0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xd2, 0xc4, 0xb0, 0xd2,
+  0x91, 0xd4, 0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd,
+  0xa8, 0x6a, 0xd4, 0xe3, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+  0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64,
+  0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0xa0, 0x39, 0xa0, 0x37, 0x86, 0x35,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61,
+  0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+  0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x72, 0x6f, 0x6f,
+  0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+  0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e,
+  0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd2, 0x86,
+  0xc0, 0xec, 0xbd, 0xf9, 0xa1, 0xb6, 0x67, 0xee, 0x66, 0x0b, 0xa2, 0x06,
+  0x3a, 0x04, 0x50, 0x8e, 0x15, 0x72, 0xac, 0x4a, 0x74, 0x95, 0x53, 0xcb,
+  0x37, 0xcb, 0x44, 0x49, 0xef, 0x07, 0x90, 0x6b, 0x33, 0xd9, 0x96, 0xf0,
+  0x94, 0x56, 0xa5, 0x13, 0x30, 0x05, 0x3c, 0x85, 0x32, 0x21, 0x7b, 0xc9,
+  0xc7, 0x0a, 0xa8, 0x24, 0xa4, 0x90, 0xde, 0x46, 0xd3, 0x25, 0x23, 0x14,
+  0x03, 0x67, 0xc2, 0x10, 0xd6, 0x6f, 0x0f, 0x5d, 0x7b, 0x7a, 0xcc, 0x9f,
+  0xc5, 0x58, 0x2a, 0xc1, 0xc4, 0x9e, 0x21, 0xa8, 0x5a, 0xf3, 0xac, 0xa4,
+  0x46, 0xf3, 0x9e, 0xe4, 0x63, 0xcb, 0x2f, 0x90, 0xa4, 0x29, 0x29, 0x01,
+  0xd9, 0x72, 0x2c, 0x29, 0xdf, 0x37, 0x01, 0x27, 0xbc, 0x4f, 0xee, 0x68,
+  0xd3, 0x21, 0x8f, 0xc0, 0xb3, 0xe4, 0xf5, 0x09, 0xed, 0xd2, 0x10, 0xaa,
+  0x53, 0xb4, 0xbe, 0xf0, 0xcc, 0x59, 0x0b, 0xd6, 0x3b, 0x96, 0x1c, 0x95,
+  0x24, 0x49, 0xdf, 0xce, 0xec, 0xfd, 0xa7, 0x48, 0x91, 0x14, 0x45, 0x0e,
+  0x3a, 0x36, 0x6f, 0xda, 0x45, 0xb3, 0x45, 0xa2, 0x41, 0xc9, 0xd4, 0xd7,
+  0x44, 0x4e, 0x3e, 0xb9, 0x74, 0x76, 0xd5, 0xa2, 0x13, 0x55, 0x2c, 0xc6,
+  0x87, 0xa3, 0xb5, 0x99, 0xac, 0x06, 0x84, 0x87, 0x7f, 0x75, 0x06, 0xfc,
+  0xbf, 0x14, 0x4c, 0x0e, 0xcc, 0x6e, 0xc4, 0xdf, 0x3d, 0xb7, 0x12, 0x71,
+  0xf4, 0xe8, 0xf1, 0x51, 0x40, 0x22, 0x28, 0x49, 0xe0, 0x1d, 0x4b, 0x87,
+  0xa8, 0x34, 0xcc, 0x06, 0xa2, 0xdd, 0x12, 0x5a, 0xd1, 0x86, 0x36, 0x64,
+  0x03, 0x35, 0x6f, 0x6f, 0x77, 0x6e, 0xeb, 0xf2, 0x85, 0x50, 0x98, 0x5e,
+  0xab, 0x03, 0x53, 0xad, 0x91, 0x23, 0x63, 0x1f, 0x16, 0x9c, 0xcd, 0xb9,
+  0xb2, 0x05, 0x63, 0x3a, 0xe1, 0xf4, 0x68, 0x1b, 0x17, 0x05, 0x35, 0x95,
+  0x53, 0xee,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            6e:ba:f0:8f:79:83:fa:9d:e1:b2:6f:96:fc:6e:98:bf
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Aug 23 00:00:00 2011 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d4:2b:2e:1c:d2:a3:f8:7f:55:14:40:de:f7:44:
+                    dd:84:55:f7:85:7b:55:66:69:a7:e5:59:eb:65:83:
+                    f4:f3:76:b1:66:c3:4f:4e:98:93:09:b7:40:b3:d1:
+                    17:a0:12:09:a8:80:e1:29:63:97:02:8c:31:9d:0a:
+                    02:e0:59:5b:bb:ed:30:b5:ef:7e:5d:af:08:4e:8d:
+                    8b:c2:39:56:16:98:73:94:78:0a:c9:a6:4f:28:b7:
+                    a8:34:37:db:25:21:b1:3c:99:f6:e0:12:3e:73:ea:
+                    64:32:9f:42:06:3c:19:d8:0a:04:7a:4c:57:49:2b:
+                    d2:77:7a:d0:00:bc:5e:fa:8e:ee:cc:c2:e4:13:6e:
+                    25:5f:dc:3c:a4:88:a3:dc:49:c7:bc:c7:0f:dd:19:
+                    c0:b1:72:ed:78:ef:38:83:0a:45:17:1b:c9:7d:9d:
+                    ed:df:ab:2c:2c:a3:75:ae:5b:82:1d:88:83:8d:ce:
+                    08:65:0c:66:26:57:05:a1:0c:df:e6:07:84:0b:84:
+                    a3:c8:ab:d5:95:47:bf:dc:dc:fe:1d:fc:02:93:44:
+                    01:ca:e6:b5:b7:6b:16:30:01:5d:e9:89:09:95:9e:
+                    f8:5e:29:5c:dd:c7:55:8c:f2:8e:20:4e:40:7a:e4:
+                    f5:45:03:b4:98:2b:c4:80:7e:53:87:6f:c2:d2:57:
+                    b0:e9
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                1B:6B:BD:1F:8A:49:18:94:54:37:55:B4:20:17:ED:37:B9:77:18:7D
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         43:25:39:23:07:04:ac:99:5d:59:67:3d:e6:2f:61:7d:5a:56:
+         7b:fc:06:8d:b3:4b:9d:fa:d5:05:4c:0d:66:b5:bd:3c:c7:a2:
+         2a:6b:b5:cf:e6:ba:83:3e:60:90:36:0c:d5:c2:ed:8a:95:d9:
+         92:42:23:1c:03:76:3e:c2:48:f1:75:72:9d:b3:8c:cf:b3:58:
+         34:56:49:1d:a1:2e:2b:3d:b2:e8:5a:10:46:de:64:b5:4d:ae:
+         4b:6e:fc:01:b7:21:10:d5:95:b7:eb:2c:be:14:06:cc:41:2e:
+         e4:6c:e2:46:90:ff:c6:28:7e:73:fe:e5:17:ba:82:c3:10:05:
+         81:66:c2:8b:28:38:a0:44:3e:e9:e4:ce:33:b0:7c:f8:e1:53:
+         9d:b8:b4:cb:da:c9:2e:d9:93:70:8e:7c:0b:e3:73:3e:99:99:
+         8f:eb:e1:11:44:35:d8:60:81:62:45:d4:de:45:5b:90:2e:49:
+         1b:1b:db:a4:0f:80:62:21:73:69:f1:e3:de:6d:d8:48:7c:56:
+         12:26:22:11:47:01:c6:5e:19:c2:b4:95:97:ee:61:00:55:f1:
+         04:38:fc:84:e6:78:b4:0d:43:be:43:33:dd:68:d3:22:5b:00:
+         fb:14:82:e8:4b:62:79:30:cf:d3:95:9f:b3:b9:84:01:d4:dd:
+         cf:23:12:f8
+-----BEGIN CERTIFICATE-----
+MIIE4jCCA8qgAwIBAgIQbrrwj3mD+p3hsm+W/G6YvzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTExMDgyMzAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+cDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxFjAUBgNV
+BAMTDUNPTU9ETyBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDUKy4c0qP4f1UUQN73RN2EVfeFe1VmaaflWetlg/TzdrFmw09OmJMJt0Cz0Reg
+EgmogOEpY5cCjDGdCgLgWVu77TC1735drwhOjYvCOVYWmHOUeArJpk8ot6g0N9sl
+IbE8mfbgEj5z6mQyn0IGPBnYCgR6TFdJK9J3etAAvF76ju7MwuQTbiVf3DykiKPc
+Sce8xw/dGcCxcu147ziDCkUXG8l9ne3fqywso3WuW4IdiIONzghlDGYmVwWhDN/m
+B4QLhKPIq9WVR7/c3P4d/AKTRAHK5rW3axYwAV3piQmVnvheKVzdx1WM8o4gTkB6
+5PVFA7SYK8SAflOHb8LSV7DpAgMBAAGjggF3MIIBczAfBgNVHSMEGDAWgBStvZh6
+NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUG2u9H4pJGJRUN1W0IBftN7l3GH0w
+DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEQYDVR0gBAowCDAG
+BgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNv
+bS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYwgaMw
+PwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4
+dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2VydHJ1
+c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8v
+b2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBDJTkjBwSsmV1Z
+Zz3mL2F9WlZ7/AaNs0ud+tUFTA1mtb08x6Iqa7XP5rqDPmCQNgzVwu2KldmSQiMc
+A3Y+wkjxdXKds4zPs1g0VkkdoS4rPbLoWhBG3mS1Ta5LbvwBtyEQ1ZW36yy+FAbM
+QS7kbOJGkP/GKH5z/uUXuoLDEAWBZsKLKDigRD7p5M4zsHz44VOduLTL2sku2ZNw
+jnwL43M+mZmP6+ERRDXYYIFiRdTeRVuQLkkbG9ukD4BiIXNp8ePebdhIfFYSJiIR
+RwHGXhnCtJWX7mEAVfEEOPyE5ni0DUO+QzPdaNMiWwD7FILoS2J5MM/TlZ+zuYQB
+1N3PIxL4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert57[] = {
+  0x30, 0x82, 0x04, 0xe2, 0x30, 0x82, 0x03, 0xca, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x6e, 0xba, 0xf0, 0x8f, 0x79, 0x83, 0xfa, 0x9d, 0xe1,
+  0xb2, 0x6f, 0x96, 0xfc, 0x6e, 0x98, 0xbf, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x38, 0x32,
+  0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x70, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x53,
+  0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xd4, 0x2b, 0x2e, 0x1c, 0xd2, 0xa3, 0xf8, 0x7f, 0x55, 0x14,
+  0x40, 0xde, 0xf7, 0x44, 0xdd, 0x84, 0x55, 0xf7, 0x85, 0x7b, 0x55, 0x66,
+  0x69, 0xa7, 0xe5, 0x59, 0xeb, 0x65, 0x83, 0xf4, 0xf3, 0x76, 0xb1, 0x66,
+  0xc3, 0x4f, 0x4e, 0x98, 0x93, 0x09, 0xb7, 0x40, 0xb3, 0xd1, 0x17, 0xa0,
+  0x12, 0x09, 0xa8, 0x80, 0xe1, 0x29, 0x63, 0x97, 0x02, 0x8c, 0x31, 0x9d,
+  0x0a, 0x02, 0xe0, 0x59, 0x5b, 0xbb, 0xed, 0x30, 0xb5, 0xef, 0x7e, 0x5d,
+  0xaf, 0x08, 0x4e, 0x8d, 0x8b, 0xc2, 0x39, 0x56, 0x16, 0x98, 0x73, 0x94,
+  0x78, 0x0a, 0xc9, 0xa6, 0x4f, 0x28, 0xb7, 0xa8, 0x34, 0x37, 0xdb, 0x25,
+  0x21, 0xb1, 0x3c, 0x99, 0xf6, 0xe0, 0x12, 0x3e, 0x73, 0xea, 0x64, 0x32,
+  0x9f, 0x42, 0x06, 0x3c, 0x19, 0xd8, 0x0a, 0x04, 0x7a, 0x4c, 0x57, 0x49,
+  0x2b, 0xd2, 0x77, 0x7a, 0xd0, 0x00, 0xbc, 0x5e, 0xfa, 0x8e, 0xee, 0xcc,
+  0xc2, 0xe4, 0x13, 0x6e, 0x25, 0x5f, 0xdc, 0x3c, 0xa4, 0x88, 0xa3, 0xdc,
+  0x49, 0xc7, 0xbc, 0xc7, 0x0f, 0xdd, 0x19, 0xc0, 0xb1, 0x72, 0xed, 0x78,
+  0xef, 0x38, 0x83, 0x0a, 0x45, 0x17, 0x1b, 0xc9, 0x7d, 0x9d, 0xed, 0xdf,
+  0xab, 0x2c, 0x2c, 0xa3, 0x75, 0xae, 0x5b, 0x82, 0x1d, 0x88, 0x83, 0x8d,
+  0xce, 0x08, 0x65, 0x0c, 0x66, 0x26, 0x57, 0x05, 0xa1, 0x0c, 0xdf, 0xe6,
+  0x07, 0x84, 0x0b, 0x84, 0xa3, 0xc8, 0xab, 0xd5, 0x95, 0x47, 0xbf, 0xdc,
+  0xdc, 0xfe, 0x1d, 0xfc, 0x02, 0x93, 0x44, 0x01, 0xca, 0xe6, 0xb5, 0xb7,
+  0x6b, 0x16, 0x30, 0x01, 0x5d, 0xe9, 0x89, 0x09, 0x95, 0x9e, 0xf8, 0x5e,
+  0x29, 0x5c, 0xdd, 0xc7, 0x55, 0x8c, 0xf2, 0x8e, 0x20, 0x4e, 0x40, 0x7a,
+  0xe4, 0xf5, 0x45, 0x03, 0xb4, 0x98, 0x2b, 0xc4, 0x80, 0x7e, 0x53, 0x87,
+  0x6f, 0xc2, 0xd2, 0x57, 0xb0, 0xe9, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a,
+  0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0,
+  0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x1b, 0x6b, 0xbd, 0x1f, 0x8a, 0x49, 0x18, 0x94, 0x54,
+  0x37, 0x55, 0xb4, 0x20, 0x17, 0xed, 0x37, 0xb9, 0x77, 0x18, 0x7d, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d,
+  0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86,
+  0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e,
+  0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78,
+  0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30,
+  0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
+  0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e,
+  0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78,
+  0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74,
+  0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e,
+  0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x43, 0x25, 0x39, 0x23, 0x07, 0x04, 0xac, 0x99, 0x5d, 0x59,
+  0x67, 0x3d, 0xe6, 0x2f, 0x61, 0x7d, 0x5a, 0x56, 0x7b, 0xfc, 0x06, 0x8d,
+  0xb3, 0x4b, 0x9d, 0xfa, 0xd5, 0x05, 0x4c, 0x0d, 0x66, 0xb5, 0xbd, 0x3c,
+  0xc7, 0xa2, 0x2a, 0x6b, 0xb5, 0xcf, 0xe6, 0xba, 0x83, 0x3e, 0x60, 0x90,
+  0x36, 0x0c, 0xd5, 0xc2, 0xed, 0x8a, 0x95, 0xd9, 0x92, 0x42, 0x23, 0x1c,
+  0x03, 0x76, 0x3e, 0xc2, 0x48, 0xf1, 0x75, 0x72, 0x9d, 0xb3, 0x8c, 0xcf,
+  0xb3, 0x58, 0x34, 0x56, 0x49, 0x1d, 0xa1, 0x2e, 0x2b, 0x3d, 0xb2, 0xe8,
+  0x5a, 0x10, 0x46, 0xde, 0x64, 0xb5, 0x4d, 0xae, 0x4b, 0x6e, 0xfc, 0x01,
+  0xb7, 0x21, 0x10, 0xd5, 0x95, 0xb7, 0xeb, 0x2c, 0xbe, 0x14, 0x06, 0xcc,
+  0x41, 0x2e, 0xe4, 0x6c, 0xe2, 0x46, 0x90, 0xff, 0xc6, 0x28, 0x7e, 0x73,
+  0xfe, 0xe5, 0x17, 0xba, 0x82, 0xc3, 0x10, 0x05, 0x81, 0x66, 0xc2, 0x8b,
+  0x28, 0x38, 0xa0, 0x44, 0x3e, 0xe9, 0xe4, 0xce, 0x33, 0xb0, 0x7c, 0xf8,
+  0xe1, 0x53, 0x9d, 0xb8, 0xb4, 0xcb, 0xda, 0xc9, 0x2e, 0xd9, 0x93, 0x70,
+  0x8e, 0x7c, 0x0b, 0xe3, 0x73, 0x3e, 0x99, 0x99, 0x8f, 0xeb, 0xe1, 0x11,
+  0x44, 0x35, 0xd8, 0x60, 0x81, 0x62, 0x45, 0xd4, 0xde, 0x45, 0x5b, 0x90,
+  0x2e, 0x49, 0x1b, 0x1b, 0xdb, 0xa4, 0x0f, 0x80, 0x62, 0x21, 0x73, 0x69,
+  0xf1, 0xe3, 0xde, 0x6d, 0xd8, 0x48, 0x7c, 0x56, 0x12, 0x26, 0x22, 0x11,
+  0x47, 0x01, 0xc6, 0x5e, 0x19, 0xc2, 0xb4, 0x95, 0x97, 0xee, 0x61, 0x00,
+  0x55, 0xf1, 0x04, 0x38, 0xfc, 0x84, 0xe6, 0x78, 0xb4, 0x0d, 0x43, 0xbe,
+  0x43, 0x33, 0xdd, 0x68, 0xd3, 0x22, 0x5b, 0x00, 0xfb, 0x14, 0x82, 0xe8,
+  0x4b, 0x62, 0x79, 0x30, 0xcf, 0xd3, 0x95, 0x9f, 0xb3, 0xb9, 0x84, 0x01,
+  0xd4, 0xdd, 0xcf, 0x23, 0x12, 0xf8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4e:6c:48:88:36:bb:28:ce:2b:e3:5a:c3:79:8f:4a:24
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Nov 14 00:00:00 2012 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO SSL CA 2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:90:3d:54:2c:85:28:dd:9b:d0:b8:2b:8d:cc:31:
+                    a5:96:97:0c:58:92:24:77:84:ad:9e:8a:92:e3:87:
+                    d9:8a:55:63:ea:ea:9e:e7:08:9d:bf:e5:8a:e9:60:
+                    53:3e:80:6f:86:52:49:10:91:70:cf:10:b3:eb:08:
+                    58:25:48:5d:5d:eb:b7:ab:de:26:c1:5b:e1:9b:04:
+                    de:5d:19:3a:be:40:17:d7:4e:dd:f9:d1:83:ca:36:
+                    36:2c:48:08:71:5c:eb:f2:0f:af:12:7a:4e:ad:2b:
+                    b7:5d:8b:4b:ec:ee:fe:df:34:69:2c:fc:73:af:b1:
+                    ce:0f:79:79:db:0a:90:02:fd:ca:33:b4:a2:d5:9d:
+                    79:5f:7f:b3:a4:59:a8:28:aa:78:e5:54:0a:18:d0:
+                    2e:6a:94:26:10:18:2b:7e:b3:cf:dd:28:28:bd:f8:
+                    8b:6b:ca:05:df:7a:50:ba:b8:4c:55:f6:79:ef:4f:
+                    c4:4c:0f:8b:dc:79:a5:be:49:9d:7a:18:aa:f1:a6:
+                    6c:f8:59:e0:41:c2:e7:7c:1d:0c:ea:be:8d:e9:c8:
+                    0f:55:22:f5:71:42:a9:d0:81:ba:92:58:95:f8:c2:
+                    ad:5a:7b:2f:00:81:d7:70:8d:b6:d7:45:f6:08:c0:
+                    8d:cb:5d:48:db:63:65:97:31:d1:15:9a:03:4f:1e:
+                    76:9d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                BF:D4:7D:6F:AF:74:93:90:88:A8:43:C6:1E:F7:13:6C:AE:B5:CC:AF
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.comodoca3.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3d:6d:89:77:8f:fe:66:51:37:17:ea:1e:f1:51:44:78:2a:f2:
+         a5:69:a9:40:0d:29:cf:35:5e:4d:bc:9e:7f:13:09:ce:cc:0e:
+         be:e0:b8:62:be:a1:18:cd:7e:91:78:ea:9b:46:dd:f7:0e:f8:
+         4c:7d:2e:48:6d:a8:6a:94:a4:73:7d:de:94:90:b0:f7:9c:ab:
+         6a:98:3c:08:45:4d:81:c6:0f:dd:9c:2b:3e:5b:b3:39:07:c4:
+         9d:34:ce:4a:c3:47:30:85:24:36:d6:48:59:84:e4:d7:06:ed:
+         a9:dc:c7:0c:ca:7b:33:ea:bf:d4:4b:88:fc:3b:3b:4a:84:8d:
+         bd:f8:ff:ae:71:43:ff:98:d3:e9:7e:7d:59:9c:98:1d:14:00:
+         4b:dc:ce:72:ce:1c:6f:dc:e7:33:d1:ca:3f:f7:8c:1e:d9:89:
+         39:52:77:86:ea:cf:66:6c:1b:6b:38:c2:cb:f8:a7:47:60:87:
+         18:d2:c0:ff:a9:b6:0a:22:41:4b:bf:55:78:ec:c1:95:2c:3c:
+         f1:7e:5b:58:d0:6c:29:f6:36:ba:dc:cb:99:49:75:4d:2a:9d:
+         a5:b5:33:5d:35:db:9b:d5:f5:b3:67:a3:db:c0:85:5f:11:33:
+         09:8f:e1:8a:42:f7:a0:da:a3:b9:7f:35:d4:6c:74:8f:df:f8:
+         ff:be:bf:8e
+-----BEGIN CERTIFICATE-----
+MIIE5DCCA8ygAwIBAgIQTmxIiDa7KM4r41rDeY9KJDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEyMTExNDAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+cjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxGDAWBgNV
+BAMTD0NPTU9ETyBTU0wgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAJA9VCyFKN2b0LgrjcwxpZaXDFiSJHeErZ6KkuOH2YpVY+rqnucInb/liulg
+Uz6Ab4ZSSRCRcM8Qs+sIWCVIXV3rt6veJsFb4ZsE3l0ZOr5AF9dO3fnRg8o2NixI
+CHFc6/IPrxJ6Tq0rt12LS+zu/t80aSz8c6+xzg95edsKkAL9yjO0otWdeV9/s6RZ
+qCiqeOVUChjQLmqUJhAYK36zz90oKL34i2vKBd96ULq4TFX2ee9PxEwPi9x5pb5J
+nXoYqvGmbPhZ4EHC53wdDOq+jenID1Ui9XFCqdCBupJYlfjCrVp7LwCB13CNttdF
+9gjAjctdSNtjZZcx0RWaA08edp0CAwEAAaOCAXcwggFzMB8GA1UdIwQYMBaAFK29
+mHo0tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBS/1H1vr3STkIioQ8Ye9xNsrrXM
+rzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADARBgNVHSAECjAI
+MAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3Qu
+Y29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGzBggrBgEFBQcBAQSBpjCB
+ozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0
+cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLmNvbW9kb2NhMy5jb20wDQYJKoZIhvcNAQEFBQADggEBAD1tiXeP/mZR
+NxfqHvFRRHgq8qVpqUANKc81Xk28nn8TCc7MDr7guGK+oRjNfpF46ptG3fcO+Ex9
+LkhtqGqUpHN93pSQsPecq2qYPAhFTYHGD92cKz5bszkHxJ00zkrDRzCFJDbWSFmE
+5NcG7ancxwzKezPqv9RLiPw7O0qEjb34/65xQ/+Y0+l+fVmcmB0UAEvcznLOHG/c
+5zPRyj/3jB7ZiTlSd4bqz2ZsG2s4wsv4p0dghxjSwP+ptgoiQUu/VXjswZUsPPF+
+W1jQbCn2Nrrcy5lJdU0qnaW1M10125vV9bNno9vAhV8RMwmP4YpC96Dao7l/NdRs
+dI/f+P++v44=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert58[] = {
+  0x30, 0x82, 0x04, 0xe4, 0x30, 0x82, 0x03, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x4e, 0x6c, 0x48, 0x88, 0x36, 0xbb, 0x28, 0xce, 0x2b,
+  0xe3, 0x5a, 0xc3, 0x79, 0x8f, 0x4a, 0x24, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x31, 0x31, 0x31,
+  0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x72, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x53,
+  0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0x90, 0x3d, 0x54, 0x2c, 0x85, 0x28, 0xdd, 0x9b,
+  0xd0, 0xb8, 0x2b, 0x8d, 0xcc, 0x31, 0xa5, 0x96, 0x97, 0x0c, 0x58, 0x92,
+  0x24, 0x77, 0x84, 0xad, 0x9e, 0x8a, 0x92, 0xe3, 0x87, 0xd9, 0x8a, 0x55,
+  0x63, 0xea, 0xea, 0x9e, 0xe7, 0x08, 0x9d, 0xbf, 0xe5, 0x8a, 0xe9, 0x60,
+  0x53, 0x3e, 0x80, 0x6f, 0x86, 0x52, 0x49, 0x10, 0x91, 0x70, 0xcf, 0x10,
+  0xb3, 0xeb, 0x08, 0x58, 0x25, 0x48, 0x5d, 0x5d, 0xeb, 0xb7, 0xab, 0xde,
+  0x26, 0xc1, 0x5b, 0xe1, 0x9b, 0x04, 0xde, 0x5d, 0x19, 0x3a, 0xbe, 0x40,
+  0x17, 0xd7, 0x4e, 0xdd, 0xf9, 0xd1, 0x83, 0xca, 0x36, 0x36, 0x2c, 0x48,
+  0x08, 0x71, 0x5c, 0xeb, 0xf2, 0x0f, 0xaf, 0x12, 0x7a, 0x4e, 0xad, 0x2b,
+  0xb7, 0x5d, 0x8b, 0x4b, 0xec, 0xee, 0xfe, 0xdf, 0x34, 0x69, 0x2c, 0xfc,
+  0x73, 0xaf, 0xb1, 0xce, 0x0f, 0x79, 0x79, 0xdb, 0x0a, 0x90, 0x02, 0xfd,
+  0xca, 0x33, 0xb4, 0xa2, 0xd5, 0x9d, 0x79, 0x5f, 0x7f, 0xb3, 0xa4, 0x59,
+  0xa8, 0x28, 0xaa, 0x78, 0xe5, 0x54, 0x0a, 0x18, 0xd0, 0x2e, 0x6a, 0x94,
+  0x26, 0x10, 0x18, 0x2b, 0x7e, 0xb3, 0xcf, 0xdd, 0x28, 0x28, 0xbd, 0xf8,
+  0x8b, 0x6b, 0xca, 0x05, 0xdf, 0x7a, 0x50, 0xba, 0xb8, 0x4c, 0x55, 0xf6,
+  0x79, 0xef, 0x4f, 0xc4, 0x4c, 0x0f, 0x8b, 0xdc, 0x79, 0xa5, 0xbe, 0x49,
+  0x9d, 0x7a, 0x18, 0xaa, 0xf1, 0xa6, 0x6c, 0xf8, 0x59, 0xe0, 0x41, 0xc2,
+  0xe7, 0x7c, 0x1d, 0x0c, 0xea, 0xbe, 0x8d, 0xe9, 0xc8, 0x0f, 0x55, 0x22,
+  0xf5, 0x71, 0x42, 0xa9, 0xd0, 0x81, 0xba, 0x92, 0x58, 0x95, 0xf8, 0xc2,
+  0xad, 0x5a, 0x7b, 0x2f, 0x00, 0x81, 0xd7, 0x70, 0x8d, 0xb6, 0xd7, 0x45,
+  0xf6, 0x08, 0xc0, 0x8d, 0xcb, 0x5d, 0x48, 0xdb, 0x63, 0x65, 0x97, 0x31,
+  0xd1, 0x15, 0x9a, 0x03, 0x4f, 0x1e, 0x76, 0x9d, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+  0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+  0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xbf, 0xd4, 0x7d, 0x6f, 0xaf, 0x74, 0x93,
+  0x90, 0x88, 0xa8, 0x43, 0xc6, 0x1e, 0xf7, 0x13, 0x6c, 0xae, 0xb5, 0xcc,
+  0xaf, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+  0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+  0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+  0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+  0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64,
+  0x6f, 0x63, 0x61, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x3d, 0x6d, 0x89, 0x77, 0x8f, 0xfe, 0x66, 0x51,
+  0x37, 0x17, 0xea, 0x1e, 0xf1, 0x51, 0x44, 0x78, 0x2a, 0xf2, 0xa5, 0x69,
+  0xa9, 0x40, 0x0d, 0x29, 0xcf, 0x35, 0x5e, 0x4d, 0xbc, 0x9e, 0x7f, 0x13,
+  0x09, 0xce, 0xcc, 0x0e, 0xbe, 0xe0, 0xb8, 0x62, 0xbe, 0xa1, 0x18, 0xcd,
+  0x7e, 0x91, 0x78, 0xea, 0x9b, 0x46, 0xdd, 0xf7, 0x0e, 0xf8, 0x4c, 0x7d,
+  0x2e, 0x48, 0x6d, 0xa8, 0x6a, 0x94, 0xa4, 0x73, 0x7d, 0xde, 0x94, 0x90,
+  0xb0, 0xf7, 0x9c, 0xab, 0x6a, 0x98, 0x3c, 0x08, 0x45, 0x4d, 0x81, 0xc6,
+  0x0f, 0xdd, 0x9c, 0x2b, 0x3e, 0x5b, 0xb3, 0x39, 0x07, 0xc4, 0x9d, 0x34,
+  0xce, 0x4a, 0xc3, 0x47, 0x30, 0x85, 0x24, 0x36, 0xd6, 0x48, 0x59, 0x84,
+  0xe4, 0xd7, 0x06, 0xed, 0xa9, 0xdc, 0xc7, 0x0c, 0xca, 0x7b, 0x33, 0xea,
+  0xbf, 0xd4, 0x4b, 0x88, 0xfc, 0x3b, 0x3b, 0x4a, 0x84, 0x8d, 0xbd, 0xf8,
+  0xff, 0xae, 0x71, 0x43, 0xff, 0x98, 0xd3, 0xe9, 0x7e, 0x7d, 0x59, 0x9c,
+  0x98, 0x1d, 0x14, 0x00, 0x4b, 0xdc, 0xce, 0x72, 0xce, 0x1c, 0x6f, 0xdc,
+  0xe7, 0x33, 0xd1, 0xca, 0x3f, 0xf7, 0x8c, 0x1e, 0xd9, 0x89, 0x39, 0x52,
+  0x77, 0x86, 0xea, 0xcf, 0x66, 0x6c, 0x1b, 0x6b, 0x38, 0xc2, 0xcb, 0xf8,
+  0xa7, 0x47, 0x60, 0x87, 0x18, 0xd2, 0xc0, 0xff, 0xa9, 0xb6, 0x0a, 0x22,
+  0x41, 0x4b, 0xbf, 0x55, 0x78, 0xec, 0xc1, 0x95, 0x2c, 0x3c, 0xf1, 0x7e,
+  0x5b, 0x58, 0xd0, 0x6c, 0x29, 0xf6, 0x36, 0xba, 0xdc, 0xcb, 0x99, 0x49,
+  0x75, 0x4d, 0x2a, 0x9d, 0xa5, 0xb5, 0x33, 0x5d, 0x35, 0xdb, 0x9b, 0xd5,
+  0xf5, 0xb3, 0x67, 0xa3, 0xdb, 0xc0, 0x85, 0x5f, 0x11, 0x33, 0x09, 0x8f,
+  0xe1, 0x8a, 0x42, 0xf7, 0xa0, 0xda, 0xa3, 0xb9, 0x7f, 0x35, 0xd4, 0x6c,
+  0x74, 0x8f, 0xdf, 0xf8, 0xff, 0xbe, 0xbf, 0x8e,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4f:e3:e2:65:21:07:ab:20:37:41:6e:48:70:ce:d2:c2
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: May 25 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Trusted Secure Certificate Authority, CN=Trusted Secure Certificate Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:80:0b:42:c6:06:6c:cf:22:b3:1a:9e:11:2e:42:
+                    6e:39:bf:e8:12:af:3c:42:21:12:95:40:5d:32:b1:
+                    6d:1c:21:d1:34:e5:4f:a8:d1:43:a2:26:4e:30:7d:
+                    73:44:2c:73:aa:c5:4d:66:01:19:d2:ea:50:59:65:
+                    d0:68:9d:05:a0:7c:a1:79:53:d0:21:90:59:0e:37:
+                    db:1e:dc:92:a7:8b:0d:c4:f5:f8:e6:ff:b5:35:1a:
+                    da:a8:b6:9b:20:85:65:c4:a2:4d:df:f3:94:4d:63:
+                    7e:ee:89:07:af:fe:e1:ba:00:15:2d:c6:77:8e:a3:
+                    fe:ad:cf:26:54:5a:df:fc:d2:de:c2:ad:f6:b2:23:
+                    fd:a8:83:e5:65:bd:27:f7:27:1a:18:59:6a:9e:14:
+                    f6:b4:86:ff:1c:58:14:43:73:96:24:bf:10:43:d5:
+                    5c:89:f0:ce:f7:e1:96:16:5e:18:4a:27:28:90:80:
+                    18:fc:32:fe:f4:c7:b8:d6:82:3d:35:af:bb:4a:1c:
+                    5b:05:78:f6:fd:55:3e:82:74:b2:73:b8:89:4e:f7:
+                    1b:85:9a:d8:ca:b1:5a:b1:00:20:41:14:30:2b:14:
+                    24:ed:37:0e:32:3e:23:88:39:7e:b9:d9:38:03:e2:
+                    4c:d9:0d:43:41:33:10:eb:30:72:53:88:f7:52:9b:
+                    4f:81
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                CC:03:5B:96:5A:9E:16:CC:26:1E:BD:A3:70:FB:E3:CB:79:19:FC:4D
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.2.8
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         7b:f0:fc:a1:28:47:bc:2b:b4:04:73:3f:4b:dd:1e:d1:b9:cd:
+         1c:ed:7d:e5:e8:cb:51:f4:92:bf:dd:9c:0d:5c:6e:1d:95:ed:
+         5b:70:50:89:d4:67:9a:15:54:d1:90:0a:fa:09:68:06:18:bb:
+         d7:27:e4:93:ff:43:48:81:3b:c8:59:49:35:ea:ac:b6:ae:46:
+         b5:d4:f3:b8:c3:c6:e4:91:bf:c9:34:fd:7e:d0:59:6e:61:a1:
+         1f:48:63:54:b2:7d:46:bf:c8:fa:c3:bf:48:58:98:f6:69:84:
+         a7:16:69:08:27:a4:22:cb:a2:2c:c8:df:6e:a9:ee:f8:41:df:
+         1b:a8:b7:f3:e3:ae:ce:a3:fe:d9:27:60:50:3f:04:7d:7a:44:
+         ea:76:42:5c:d3:55:46:ef:27:c5:6a:4a:80:e7:35:a0:91:c6:
+         1b:a6:86:9c:5a:3b:04:83:54:34:d7:d1:88:a6:36:e9:7f:40:
+         27:da:56:0a:50:21:9d:29:8b:a0:84:ec:fe:71:23:53:04:18:
+         19:70:67:86:44:95:72:40:55:f6:dd:a3:b4:3d:2d:09:60:a5:
+         e7:5f:fc:ac:3b:ec:0c:91:9f:f8:ee:6a:ba:b2:3c:fd:95:7d:
+         9a:07:f4:b0:65:43:a2:f6:df:7d:b8:21:49:84:04:ee:bd:ce:
+         53:8f:0f:29
+-----BEGIN CERTIFICATE-----
+MIIE5DCCA8ygAwIBAgIQT+PiZSEHqyA3QW5IcM7SwjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDUyNTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+azELMAkGA1UEBhMCVVMxLTArBgNVBAoTJFRydXN0ZWQgU2VjdXJlIENlcnRpZmlj
+YXRlIEF1dGhvcml0eTEtMCsGA1UEAxMkVHJ1c3RlZCBTZWN1cmUgQ2VydGlmaWNh
+dGUgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgAtC
+xgZszyKzGp4RLkJuOb/oEq88QiESlUBdMrFtHCHRNOVPqNFDoiZOMH1zRCxzqsVN
+ZgEZ0upQWWXQaJ0FoHyheVPQIZBZDjfbHtySp4sNxPX45v+1NRraqLabIIVlxKJN
+3/OUTWN+7okHr/7hugAVLcZ3jqP+rc8mVFrf/NLewq32siP9qIPlZb0n9ycaGFlq
+nhT2tIb/HFgUQ3OWJL8QQ9VcifDO9+GWFl4YSicokIAY/DL+9Me41oI9Na+7Shxb
+BXj2/VU+gnSyc7iJTvcbhZrYyrFasQAgQRQwKxQk7TcOMj4jiDl+udk4A+JM2Q1D
+QTMQ6zByU4j3UptPgQIDAQABo4IBfjCCAXowHwYDVR0jBBgwFoAUrb2YejS0Jvf6
+xCZU7wO94CTLVBowHQYDVR0OBBYEFMwDW5ZanhbMJh69o3D748t5GfxNMA4GA1Ud
+DwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMBgGA1UdIAQRMA8wDQYLKwYB
+BAGyMQECAggwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3Qu
+Y29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGzBggrBgEFBQcBAQSBpjCB
+ozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0
+cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAHvw/KEoR7wr
+tARzP0vdHtG5zRztfeXoy1H0kr/dnA1cbh2V7VtwUInUZ5oVVNGQCvoJaAYYu9cn
+5JP/Q0iBO8hZSTXqrLauRrXU87jDxuSRv8k0/X7QWW5hoR9IY1SyfUa/yPrDv0hY
+mPZphKcWaQgnpCLLoizI326p7vhB3xuot/Pjrs6j/tknYFA/BH16ROp2QlzTVUbv
+J8VqSoDnNaCRxhumhpxaOwSDVDTX0YimNul/QCfaVgpQIZ0pi6CE7P5xI1MEGBlw
+Z4ZElXJAVfbdo7Q9LQlgpedf/Kw77AyRn/juarqyPP2VfZoH9LBlQ6L23324IUmE
+BO69zlOPDyk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert59[] = {
+  0x30, 0x82, 0x04, 0xe4, 0x30, 0x82, 0x03, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x4f, 0xe3, 0xe2, 0x65, 0x21, 0x07, 0xab, 0x20, 0x37,
+  0x41, 0x6e, 0x48, 0x70, 0xce, 0xd2, 0xc2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x35, 0x32,
+  0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x24, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+  0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+  0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x80, 0x0b, 0x42,
+  0xc6, 0x06, 0x6c, 0xcf, 0x22, 0xb3, 0x1a, 0x9e, 0x11, 0x2e, 0x42, 0x6e,
+  0x39, 0xbf, 0xe8, 0x12, 0xaf, 0x3c, 0x42, 0x21, 0x12, 0x95, 0x40, 0x5d,
+  0x32, 0xb1, 0x6d, 0x1c, 0x21, 0xd1, 0x34, 0xe5, 0x4f, 0xa8, 0xd1, 0x43,
+  0xa2, 0x26, 0x4e, 0x30, 0x7d, 0x73, 0x44, 0x2c, 0x73, 0xaa, 0xc5, 0x4d,
+  0x66, 0x01, 0x19, 0xd2, 0xea, 0x50, 0x59, 0x65, 0xd0, 0x68, 0x9d, 0x05,
+  0xa0, 0x7c, 0xa1, 0x79, 0x53, 0xd0, 0x21, 0x90, 0x59, 0x0e, 0x37, 0xdb,
+  0x1e, 0xdc, 0x92, 0xa7, 0x8b, 0x0d, 0xc4, 0xf5, 0xf8, 0xe6, 0xff, 0xb5,
+  0x35, 0x1a, 0xda, 0xa8, 0xb6, 0x9b, 0x20, 0x85, 0x65, 0xc4, 0xa2, 0x4d,
+  0xdf, 0xf3, 0x94, 0x4d, 0x63, 0x7e, 0xee, 0x89, 0x07, 0xaf, 0xfe, 0xe1,
+  0xba, 0x00, 0x15, 0x2d, 0xc6, 0x77, 0x8e, 0xa3, 0xfe, 0xad, 0xcf, 0x26,
+  0x54, 0x5a, 0xdf, 0xfc, 0xd2, 0xde, 0xc2, 0xad, 0xf6, 0xb2, 0x23, 0xfd,
+  0xa8, 0x83, 0xe5, 0x65, 0xbd, 0x27, 0xf7, 0x27, 0x1a, 0x18, 0x59, 0x6a,
+  0x9e, 0x14, 0xf6, 0xb4, 0x86, 0xff, 0x1c, 0x58, 0x14, 0x43, 0x73, 0x96,
+  0x24, 0xbf, 0x10, 0x43, 0xd5, 0x5c, 0x89, 0xf0, 0xce, 0xf7, 0xe1, 0x96,
+  0x16, 0x5e, 0x18, 0x4a, 0x27, 0x28, 0x90, 0x80, 0x18, 0xfc, 0x32, 0xfe,
+  0xf4, 0xc7, 0xb8, 0xd6, 0x82, 0x3d, 0x35, 0xaf, 0xbb, 0x4a, 0x1c, 0x5b,
+  0x05, 0x78, 0xf6, 0xfd, 0x55, 0x3e, 0x82, 0x74, 0xb2, 0x73, 0xb8, 0x89,
+  0x4e, 0xf7, 0x1b, 0x85, 0x9a, 0xd8, 0xca, 0xb1, 0x5a, 0xb1, 0x00, 0x20,
+  0x41, 0x14, 0x30, 0x2b, 0x14, 0x24, 0xed, 0x37, 0x0e, 0x32, 0x3e, 0x23,
+  0x88, 0x39, 0x7e, 0xb9, 0xd9, 0x38, 0x03, 0xe2, 0x4c, 0xd9, 0x0d, 0x43,
+  0x41, 0x33, 0x10, 0xeb, 0x30, 0x72, 0x53, 0x88, 0xf7, 0x52, 0x9b, 0x4f,
+  0x81, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7e, 0x30, 0x82,
+  0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+  0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa,
+  0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcc, 0x03,
+  0x5b, 0x96, 0x5a, 0x9e, 0x16, 0xcc, 0x26, 0x1e, 0xbd, 0xa3, 0x70, 0xfb,
+  0xe3, 0xcb, 0x79, 0x19, 0xfc, 0x4d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06,
+  0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x08, 0x30, 0x44, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+  0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+  0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+  0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x7b, 0xf0, 0xfc, 0xa1, 0x28, 0x47, 0xbc, 0x2b,
+  0xb4, 0x04, 0x73, 0x3f, 0x4b, 0xdd, 0x1e, 0xd1, 0xb9, 0xcd, 0x1c, 0xed,
+  0x7d, 0xe5, 0xe8, 0xcb, 0x51, 0xf4, 0x92, 0xbf, 0xdd, 0x9c, 0x0d, 0x5c,
+  0x6e, 0x1d, 0x95, 0xed, 0x5b, 0x70, 0x50, 0x89, 0xd4, 0x67, 0x9a, 0x15,
+  0x54, 0xd1, 0x90, 0x0a, 0xfa, 0x09, 0x68, 0x06, 0x18, 0xbb, 0xd7, 0x27,
+  0xe4, 0x93, 0xff, 0x43, 0x48, 0x81, 0x3b, 0xc8, 0x59, 0x49, 0x35, 0xea,
+  0xac, 0xb6, 0xae, 0x46, 0xb5, 0xd4, 0xf3, 0xb8, 0xc3, 0xc6, 0xe4, 0x91,
+  0xbf, 0xc9, 0x34, 0xfd, 0x7e, 0xd0, 0x59, 0x6e, 0x61, 0xa1, 0x1f, 0x48,
+  0x63, 0x54, 0xb2, 0x7d, 0x46, 0xbf, 0xc8, 0xfa, 0xc3, 0xbf, 0x48, 0x58,
+  0x98, 0xf6, 0x69, 0x84, 0xa7, 0x16, 0x69, 0x08, 0x27, 0xa4, 0x22, 0xcb,
+  0xa2, 0x2c, 0xc8, 0xdf, 0x6e, 0xa9, 0xee, 0xf8, 0x41, 0xdf, 0x1b, 0xa8,
+  0xb7, 0xf3, 0xe3, 0xae, 0xce, 0xa3, 0xfe, 0xd9, 0x27, 0x60, 0x50, 0x3f,
+  0x04, 0x7d, 0x7a, 0x44, 0xea, 0x76, 0x42, 0x5c, 0xd3, 0x55, 0x46, 0xef,
+  0x27, 0xc5, 0x6a, 0x4a, 0x80, 0xe7, 0x35, 0xa0, 0x91, 0xc6, 0x1b, 0xa6,
+  0x86, 0x9c, 0x5a, 0x3b, 0x04, 0x83, 0x54, 0x34, 0xd7, 0xd1, 0x88, 0xa6,
+  0x36, 0xe9, 0x7f, 0x40, 0x27, 0xda, 0x56, 0x0a, 0x50, 0x21, 0x9d, 0x29,
+  0x8b, 0xa0, 0x84, 0xec, 0xfe, 0x71, 0x23, 0x53, 0x04, 0x18, 0x19, 0x70,
+  0x67, 0x86, 0x44, 0x95, 0x72, 0x40, 0x55, 0xf6, 0xdd, 0xa3, 0xb4, 0x3d,
+  0x2d, 0x09, 0x60, 0xa5, 0xe7, 0x5f, 0xfc, 0xac, 0x3b, 0xec, 0x0c, 0x91,
+  0x9f, 0xf8, 0xee, 0x6a, 0xba, 0xb2, 0x3c, 0xfd, 0x95, 0x7d, 0x9a, 0x07,
+  0xf4, 0xb0, 0x65, 0x43, 0xa2, 0xf6, 0xdf, 0x7d, 0xb8, 0x21, 0x49, 0x84,
+  0x04, 0xee, 0xbd, 0xce, 0x53, 0x8f, 0x0f, 0x29,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            07:6f:12:46:81:45:9c:28:d5:48:d6:97:c4:0e:00:1b
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Feb 16 00:00:00 2012 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=PositiveSSL CA 2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e8:ea:39:e3:22:a6:aa:b9:c4:00:d0:e7:aa:67:
+                    3b:43:07:bd:4f:92:eb:bc:be:01:a3:40:ad:e0:ef:
+                    44:28:b5:d0:3a:be:80:54:17:85:7a:6b:84:6c:36:
+                    36:e5:a3:24:e2:fe:28:01:90:bc:d7:dd:0f:b9:2b:
+                    4e:48:77:05:69:af:de:57:30:b1:e8:fb:1a:03:f6:
+                    3c:5b:53:1e:a1:01:49:68:72:73:d6:33:2b:43:a9:
+                    37:32:52:0f:ae:27:56:31:30:60:ad:c9:bd:73:2c:
+                    39:ee:90:d8:75:b0:25:21:60:7b:2a:7f:02:fd:82:
+                    85:1f:74:4f:92:34:73:5c:1d:00:a0:b0:c0:ea:98:
+                    e2:be:01:14:58:17:28:22:8a:77:5d:50:25:cd:9a:
+                    6c:a6:e5:0c:e5:ab:28:c3:b2:20:89:f0:07:24:1e:
+                    95:c2:2e:c0:e5:e9:ec:f6:3d:12:07:48:3d:d2:c3:
+                    23:56:41:ec:d3:df:35:4b:c8:e7:f6:86:05:52:10:
+                    43:9a:8c:17:7c:8b:aa:bc:78:e0:f0:45:3b:ac:80:
+                    55:fe:28:93:e1:0a:11:68:f4:52:57:6f:fe:48:0b:
+                    5b:5d:1a:6a:67:73:99:82:b4:9e:43:60:3e:c7:5b:
+                    2a:12:6e:1a:ee:cb:39:ae:c3:35:9d:a8:bc:5d:b0:
+                    2f:c3
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                99:E4:40:5F:6B:14:5E:3E:05:D9:DD:D3:63:54:FC:62:B8:F7:00:AC
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         9c:36:e3:4e:ae:f1:8a:bb:6c:97:8c:8f:4b:67:d0:9f:d8:84:
+         aa:9f:21:5f:35:a1:5b:c4:2b:63:0d:e8:bc:77:5d:a7:c4:37:
+         fd:4b:2d:9e:e8:1d:69:a1:c0:84:cc:d1:6d:8b:f3:81:cb:9f:
+         4b:74:b0:49:2a:31:e8:37:40:eb:1f:d9:97:a3:1a:11:d5:26:
+         a7:6e:0f:ba:d5:be:2c:fd:b4:91:64:dc:be:3b:19:50:0d:7a:
+         95:f3:04:13:a9:bb:47:0f:8b:5c:d1:ac:c2:7b:77:21:50:dd:
+         5b:ab:ee:f4:a6:d8:d4:4a:53:6b:4d:ad:b8:c8:e7:e6:52:58:
+         4d:43:4c:c2:a2:23:4f:0e:c0:20:39:af:df:4f:42:5b:1e:d3:
+         09:f4:18:09:59:2a:d9:e8:4a:18:bf:32:fb:fa:2d:64:8b:87:
+         ca:5b:2b:e8:b8:0b:7e:be:17:12:c7:03:82:29:af:58:af:85:
+         84:5d:3d:0a:df:23:51:c3:cd:af:10:bf:80:69:77:91:0a:4f:
+         e5:ba:e1:ad:9b:ce:df:33:4e:30:3b:e9:8f:66:7f:82:fa:6b:
+         fa:db:a3:c0:73:00:e3:d6:12:af:4d:f2:0f:5a:14:51:1f:6d:
+         b8:86:81:62:07:ce:5c:72:c2:4f:f3:57:2a:71:d9:d4:97:85:
+         e6:18:53:b7
+-----BEGIN CERTIFICATE-----
+MIIE5TCCA82gAwIBAgIQB28SRoFFnCjVSNaXxA4AGzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEyMDIxNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+czELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxGTAXBgNV
+BAMTEFBvc2l0aXZlU1NMIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDo6jnjIqaqucQA0OeqZztDB71Pkuu8vgGjQK3g70QotdA6voBUF4V6a4Rs
+NjbloyTi/igBkLzX3Q+5K05IdwVpr95XMLHo+xoD9jxbUx6hAUlocnPWMytDqTcy
+Ug+uJ1YxMGCtyb1zLDnukNh1sCUhYHsqfwL9goUfdE+SNHNcHQCgsMDqmOK+ARRY
+FygiinddUCXNmmym5QzlqyjDsiCJ8AckHpXCLsDl6ez2PRIHSD3SwyNWQezT3zVL
+yOf2hgVSEEOajBd8i6q8eODwRTusgFX+KJPhChFo9FJXb/5IC1tdGmpnc5mCtJ5D
+YD7HWyoSbhruyzmuwzWdqLxdsC/DAgMBAAGjggF3MIIBczAfBgNVHSMEGDAWgBSt
+vZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUmeRAX2sUXj4F2d3TY1T8Yrj3
+AKwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEQYDVR0gBAow
+CDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYw
+gaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVz
+dEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2Vy
+dHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRw
+Oi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQCcNuNOrvGK
+u2yXjI9LZ9Cf2ISqnyFfNaFbxCtjDei8d12nxDf9Sy2e6B1pocCEzNFti/OBy59L
+dLBJKjHoN0DrH9mXoxoR1Sanbg+61b4s/bSRZNy+OxlQDXqV8wQTqbtHD4tc0azC
+e3chUN1bq+70ptjUSlNrTa24yOfmUlhNQ0zCoiNPDsAgOa/fT0JbHtMJ9BgJWSrZ
+6EoYvzL7+i1ki4fKWyvouAt+vhcSxwOCKa9Yr4WEXT0K3yNRw82vEL+AaXeRCk/l
+uuGtm87fM04wO+mPZn+C+mv626PAcwDj1hKvTfIPWhRRH224hoFiB85ccsJP81cq
+cdnUl4XmGFO3
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert60[] = {
+  0x30, 0x82, 0x04, 0xe5, 0x30, 0x82, 0x03, 0xcd, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x07, 0x6f, 0x12, 0x46, 0x81, 0x45, 0x9c, 0x28, 0xd5,
+  0x48, 0xd6, 0x97, 0xc4, 0x0e, 0x00, 0x1b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x30, 0x32, 0x31,
+  0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x73, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x10, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65,
+  0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xe8, 0xea, 0x39, 0xe3, 0x22, 0xa6, 0xaa,
+  0xb9, 0xc4, 0x00, 0xd0, 0xe7, 0xaa, 0x67, 0x3b, 0x43, 0x07, 0xbd, 0x4f,
+  0x92, 0xeb, 0xbc, 0xbe, 0x01, 0xa3, 0x40, 0xad, 0xe0, 0xef, 0x44, 0x28,
+  0xb5, 0xd0, 0x3a, 0xbe, 0x80, 0x54, 0x17, 0x85, 0x7a, 0x6b, 0x84, 0x6c,
+  0x36, 0x36, 0xe5, 0xa3, 0x24, 0xe2, 0xfe, 0x28, 0x01, 0x90, 0xbc, 0xd7,
+  0xdd, 0x0f, 0xb9, 0x2b, 0x4e, 0x48, 0x77, 0x05, 0x69, 0xaf, 0xde, 0x57,
+  0x30, 0xb1, 0xe8, 0xfb, 0x1a, 0x03, 0xf6, 0x3c, 0x5b, 0x53, 0x1e, 0xa1,
+  0x01, 0x49, 0x68, 0x72, 0x73, 0xd6, 0x33, 0x2b, 0x43, 0xa9, 0x37, 0x32,
+  0x52, 0x0f, 0xae, 0x27, 0x56, 0x31, 0x30, 0x60, 0xad, 0xc9, 0xbd, 0x73,
+  0x2c, 0x39, 0xee, 0x90, 0xd8, 0x75, 0xb0, 0x25, 0x21, 0x60, 0x7b, 0x2a,
+  0x7f, 0x02, 0xfd, 0x82, 0x85, 0x1f, 0x74, 0x4f, 0x92, 0x34, 0x73, 0x5c,
+  0x1d, 0x00, 0xa0, 0xb0, 0xc0, 0xea, 0x98, 0xe2, 0xbe, 0x01, 0x14, 0x58,
+  0x17, 0x28, 0x22, 0x8a, 0x77, 0x5d, 0x50, 0x25, 0xcd, 0x9a, 0x6c, 0xa6,
+  0xe5, 0x0c, 0xe5, 0xab, 0x28, 0xc3, 0xb2, 0x20, 0x89, 0xf0, 0x07, 0x24,
+  0x1e, 0x95, 0xc2, 0x2e, 0xc0, 0xe5, 0xe9, 0xec, 0xf6, 0x3d, 0x12, 0x07,
+  0x48, 0x3d, 0xd2, 0xc3, 0x23, 0x56, 0x41, 0xec, 0xd3, 0xdf, 0x35, 0x4b,
+  0xc8, 0xe7, 0xf6, 0x86, 0x05, 0x52, 0x10, 0x43, 0x9a, 0x8c, 0x17, 0x7c,
+  0x8b, 0xaa, 0xbc, 0x78, 0xe0, 0xf0, 0x45, 0x3b, 0xac, 0x80, 0x55, 0xfe,
+  0x28, 0x93, 0xe1, 0x0a, 0x11, 0x68, 0xf4, 0x52, 0x57, 0x6f, 0xfe, 0x48,
+  0x0b, 0x5b, 0x5d, 0x1a, 0x6a, 0x67, 0x73, 0x99, 0x82, 0xb4, 0x9e, 0x43,
+  0x60, 0x3e, 0xc7, 0x5b, 0x2a, 0x12, 0x6e, 0x1a, 0xee, 0xcb, 0x39, 0xae,
+  0xc3, 0x35, 0x9d, 0xa8, 0xbc, 0x5d, 0xb0, 0x2f, 0xc3, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad,
+  0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef,
+  0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x99, 0xe4, 0x40, 0x5f, 0x6b, 0x14,
+  0x5e, 0x3e, 0x05, 0xd9, 0xdd, 0xd3, 0x63, 0x54, 0xfc, 0x62, 0xb8, 0xf7,
+  0x00, 0xac, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+  0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+  0x01, 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+  0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+  0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+  0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+  0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x01, 0x00, 0x9c, 0x36, 0xe3, 0x4e, 0xae, 0xf1, 0x8a,
+  0xbb, 0x6c, 0x97, 0x8c, 0x8f, 0x4b, 0x67, 0xd0, 0x9f, 0xd8, 0x84, 0xaa,
+  0x9f, 0x21, 0x5f, 0x35, 0xa1, 0x5b, 0xc4, 0x2b, 0x63, 0x0d, 0xe8, 0xbc,
+  0x77, 0x5d, 0xa7, 0xc4, 0x37, 0xfd, 0x4b, 0x2d, 0x9e, 0xe8, 0x1d, 0x69,
+  0xa1, 0xc0, 0x84, 0xcc, 0xd1, 0x6d, 0x8b, 0xf3, 0x81, 0xcb, 0x9f, 0x4b,
+  0x74, 0xb0, 0x49, 0x2a, 0x31, 0xe8, 0x37, 0x40, 0xeb, 0x1f, 0xd9, 0x97,
+  0xa3, 0x1a, 0x11, 0xd5, 0x26, 0xa7, 0x6e, 0x0f, 0xba, 0xd5, 0xbe, 0x2c,
+  0xfd, 0xb4, 0x91, 0x64, 0xdc, 0xbe, 0x3b, 0x19, 0x50, 0x0d, 0x7a, 0x95,
+  0xf3, 0x04, 0x13, 0xa9, 0xbb, 0x47, 0x0f, 0x8b, 0x5c, 0xd1, 0xac, 0xc2,
+  0x7b, 0x77, 0x21, 0x50, 0xdd, 0x5b, 0xab, 0xee, 0xf4, 0xa6, 0xd8, 0xd4,
+  0x4a, 0x53, 0x6b, 0x4d, 0xad, 0xb8, 0xc8, 0xe7, 0xe6, 0x52, 0x58, 0x4d,
+  0x43, 0x4c, 0xc2, 0xa2, 0x23, 0x4f, 0x0e, 0xc0, 0x20, 0x39, 0xaf, 0xdf,
+  0x4f, 0x42, 0x5b, 0x1e, 0xd3, 0x09, 0xf4, 0x18, 0x09, 0x59, 0x2a, 0xd9,
+  0xe8, 0x4a, 0x18, 0xbf, 0x32, 0xfb, 0xfa, 0x2d, 0x64, 0x8b, 0x87, 0xca,
+  0x5b, 0x2b, 0xe8, 0xb8, 0x0b, 0x7e, 0xbe, 0x17, 0x12, 0xc7, 0x03, 0x82,
+  0x29, 0xaf, 0x58, 0xaf, 0x85, 0x84, 0x5d, 0x3d, 0x0a, 0xdf, 0x23, 0x51,
+  0xc3, 0xcd, 0xaf, 0x10, 0xbf, 0x80, 0x69, 0x77, 0x91, 0x0a, 0x4f, 0xe5,
+  0xba, 0xe1, 0xad, 0x9b, 0xce, 0xdf, 0x33, 0x4e, 0x30, 0x3b, 0xe9, 0x8f,
+  0x66, 0x7f, 0x82, 0xfa, 0x6b, 0xfa, 0xdb, 0xa3, 0xc0, 0x73, 0x00, 0xe3,
+  0xd6, 0x12, 0xaf, 0x4d, 0xf2, 0x0f, 0x5a, 0x14, 0x51, 0x1f, 0x6d, 0xb8,
+  0x86, 0x81, 0x62, 0x07, 0xce, 0x5c, 0x72, 0xc2, 0x4f, 0xf3, 0x57, 0x2a,
+  0x71, 0xd9, 0xd4, 0x97, 0x85, 0xe6, 0x18, 0x53, 0xb7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            74:86:21:96:95:10:c9:29:26:29:4b:cc:8b:f8:29:2c
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Jun 22 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Globe Hosting, Inc., OU=GlobeSSL DV Certification Authority, CN=GlobeSSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a0:47:04:ce:a8:35:ab:ff:18:63:88:8e:c1:89:
+                    fb:2d:03:0f:cd:2d:28:97:a1:da:d2:10:5b:83:4f:
+                    2c:46:98:0b:12:98:f4:b3:39:b7:97:a3:86:5d:80:
+                    22:02:33:c8:9d:ba:9b:ac:ba:d9:7c:7e:de:f7:a3:
+                    b9:2a:69:e8:17:7b:fc:56:2f:99:87:da:2a:a2:77:
+                    a4:ac:56:8c:ac:f5:1e:df:38:4f:97:d2:3a:03:6a:
+                    f6:49:c3:2a:7b:b3:26:54:4c:15:e1:00:f3:02:90:
+                    38:a4:99:57:db:fd:8a:91:01:f1:71:96:75:df:21:
+                    f9:15:19:5d:18:2b:0e:73:10:2d:0e:5b:56:28:cb:
+                    fd:61:ec:b6:f0:5b:f9:3c:14:f6:42:0b:ca:cd:17:
+                    df:d9:76:5f:d6:54:29:40:d1:79:15:fb:f5:45:a9:
+                    2d:6f:54:35:d7:5e:39:e6:a6:b5:04:5b:90:d9:6f:
+                    5c:2f:58:85:00:00:f0:68:11:08:19:40:50:49:8c:
+                    da:87:e9:82:99:d3:86:ae:d4:c1:36:a2:56:0e:08:
+                    c3:b7:36:7a:91:f0:24:0c:79:8f:30:a5:e2:4c:99:
+                    9d:7e:76:dd:98:81:e8:49:46:ac:01:c8:25:f7:7e:
+                    04:c5:9e:fa:0d:e2:f7:b8:40:f1:45:fc:e6:c2:c9:
+                    6f:c7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                C3:AB:A0:02:F0:9B:F5:66:7F:28:15:92:22:95:DB:B8:4E:D3:93:08
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.2.27
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         66:9c:13:6d:d2:7e:2c:dd:6b:d6:1a:91:37:85:86:51:23:4d:
+         64:63:f5:5e:83:3e:88:fe:6f:67:87:0e:ca:85:6d:bb:3b:3c:
+         d5:ad:fc:ba:4d:ba:8b:bb:c8:c1:ed:2c:d4:6d:cb:10:c2:33:
+         e3:e7:66:97:8f:2b:e5:8f:81:8f:ed:bc:dd:87:b5:db:dc:23:
+         5f:ae:0f:40:91:29:9e:07:d4:b1:ce:d0:82:1b:6e:1d:d1:a4:
+         08:50:12:ae:8f:0f:79:67:a7:00:67:de:ba:90:9b:48:bc:5f:
+         90:c3:1b:fe:cc:b6:3a:1e:db:15:15:b5:de:ab:78:e3:41:aa:
+         93:8a:e1:bf:43:15:ec:c9:6b:21:fe:ed:a1:df:e9:0b:2d:cb:
+         a0:73:1f:d6:3e:f8:98:9b:46:78:e4:ad:25:20:41:86:28:d0:
+         de:7d:14:96:04:47:ac:c8:b9:6b:dd:00:f0:47:11:9f:8b:7e:
+         b1:a2:ed:47:e9:17:23:34:e6:bd:8b:67:41:64:60:0a:1a:cd:
+         75:69:89:39:66:95:e1:32:87:73:91:d0:9b:83:8d:b8:c7:e0:
+         bc:22:8f:2c:24:13:c8:c2:94:97:fa:31:26:22:82:2b:b5:ef:
+         05:a6:a0:7e:9a:00:b4:6b:e3:9e:59:43:bc:76:98:f3:3c:30:
+         db:1c:30:2e
+-----BEGIN CERTIFICATE-----
+MIIE6DCCA9CgAwIBAgIQdIYhlpUQySkmKUvMi/gpLDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDYyMjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+bzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0dsb2JlIEhvc3RpbmcsIEluYy4xLDAq
+BgNVBAsTI0dsb2JlU1NMIERWIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRQwEgYD
+VQQDEwtHbG9iZVNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKBHBM6oNav/GGOIjsGJ+y0DD80tKJeh2tIQW4NPLEaYCxKY9LM5t5ejhl2AIgIz
+yJ26m6y62Xx+3vejuSpp6Bd7/FYvmYfaKqJ3pKxWjKz1Ht84T5fSOgNq9knDKnuz
+JlRMFeEA8wKQOKSZV9v9ipEB8XGWdd8h+RUZXRgrDnMQLQ5bVijL/WHstvBb+TwU
+9kILys0X39l2X9ZUKUDReRX79UWpLW9UNddeOeamtQRbkNlvXC9YhQAA8GgRCBlA
+UEmM2ofpgpnThq7UwTaiVg4Iw7c2epHwJAx5jzCl4kyZnX523ZiB6ElGrAHIJfd+
+BMWe+g3i97hA8UX85sLJb8cCAwEAAaOCAX4wggF6MB8GA1UdIwQYMBaAFK29mHo0
+tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBTDq6AC8Jv1Zn8oFZIildu4TtOTCDAO
+BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAYBgNVHSAEETAPMA0G
+CysGAQQBsjEBAgIbMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRy
+dXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEE
+gaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRU
+cnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51
+c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlo
+dHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBmnBNt
+0n4s3WvWGpE3hYZRI01kY/Vegz6I/m9nhw7KhW27OzzVrfy6TbqLu8jB7SzUbcsQ
+wjPj52aXjyvlj4GP7bzdh7Xb3CNfrg9AkSmeB9SxztCCG24d0aQIUBKujw95Z6cA
+Z966kJtIvF+Qwxv+zLY6HtsVFbXeq3jjQaqTiuG/QxXsyWsh/u2h3+kLLcugcx/W
+PviYm0Z45K0lIEGGKNDefRSWBEesyLlr3QDwRxGfi36xou1H6RcjNOa9i2dBZGAK
+Gs11aYk5ZpXhModzkdCbg424x+C8Io8sJBPIwpSX+jEmIoIrte8FpqB+mgC0a+Oe
+WUO8dpjzPDDbHDAu
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert61[] = {
+  0x30, 0x82, 0x04, 0xe8, 0x30, 0x82, 0x03, 0xd0, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x74, 0x86, 0x21, 0x96, 0x95, 0x10, 0xc9, 0x29, 0x26,
+  0x29, 0x4b, 0xcc, 0x8b, 0xf8, 0x29, 0x2c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x36, 0x32,
+  0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x13, 0x47, 0x6c, 0x6f, 0x62, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x69,
+  0x6e, 0x67, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2c, 0x30, 0x2a,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x23, 0x47, 0x6c, 0x6f, 0x62, 0x65,
+  0x53, 0x53, 0x4c, 0x20, 0x44, 0x56, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x0b, 0x47, 0x6c, 0x6f, 0x62, 0x65, 0x53, 0x53,
+  0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xa0, 0x47, 0x04, 0xce, 0xa8, 0x35, 0xab, 0xff, 0x18, 0x63, 0x88,
+  0x8e, 0xc1, 0x89, 0xfb, 0x2d, 0x03, 0x0f, 0xcd, 0x2d, 0x28, 0x97, 0xa1,
+  0xda, 0xd2, 0x10, 0x5b, 0x83, 0x4f, 0x2c, 0x46, 0x98, 0x0b, 0x12, 0x98,
+  0xf4, 0xb3, 0x39, 0xb7, 0x97, 0xa3, 0x86, 0x5d, 0x80, 0x22, 0x02, 0x33,
+  0xc8, 0x9d, 0xba, 0x9b, 0xac, 0xba, 0xd9, 0x7c, 0x7e, 0xde, 0xf7, 0xa3,
+  0xb9, 0x2a, 0x69, 0xe8, 0x17, 0x7b, 0xfc, 0x56, 0x2f, 0x99, 0x87, 0xda,
+  0x2a, 0xa2, 0x77, 0xa4, 0xac, 0x56, 0x8c, 0xac, 0xf5, 0x1e, 0xdf, 0x38,
+  0x4f, 0x97, 0xd2, 0x3a, 0x03, 0x6a, 0xf6, 0x49, 0xc3, 0x2a, 0x7b, 0xb3,
+  0x26, 0x54, 0x4c, 0x15, 0xe1, 0x00, 0xf3, 0x02, 0x90, 0x38, 0xa4, 0x99,
+  0x57, 0xdb, 0xfd, 0x8a, 0x91, 0x01, 0xf1, 0x71, 0x96, 0x75, 0xdf, 0x21,
+  0xf9, 0x15, 0x19, 0x5d, 0x18, 0x2b, 0x0e, 0x73, 0x10, 0x2d, 0x0e, 0x5b,
+  0x56, 0x28, 0xcb, 0xfd, 0x61, 0xec, 0xb6, 0xf0, 0x5b, 0xf9, 0x3c, 0x14,
+  0xf6, 0x42, 0x0b, 0xca, 0xcd, 0x17, 0xdf, 0xd9, 0x76, 0x5f, 0xd6, 0x54,
+  0x29, 0x40, 0xd1, 0x79, 0x15, 0xfb, 0xf5, 0x45, 0xa9, 0x2d, 0x6f, 0x54,
+  0x35, 0xd7, 0x5e, 0x39, 0xe6, 0xa6, 0xb5, 0x04, 0x5b, 0x90, 0xd9, 0x6f,
+  0x5c, 0x2f, 0x58, 0x85, 0x00, 0x00, 0xf0, 0x68, 0x11, 0x08, 0x19, 0x40,
+  0x50, 0x49, 0x8c, 0xda, 0x87, 0xe9, 0x82, 0x99, 0xd3, 0x86, 0xae, 0xd4,
+  0xc1, 0x36, 0xa2, 0x56, 0x0e, 0x08, 0xc3, 0xb7, 0x36, 0x7a, 0x91, 0xf0,
+  0x24, 0x0c, 0x79, 0x8f, 0x30, 0xa5, 0xe2, 0x4c, 0x99, 0x9d, 0x7e, 0x76,
+  0xdd, 0x98, 0x81, 0xe8, 0x49, 0x46, 0xac, 0x01, 0xc8, 0x25, 0xf7, 0x7e,
+  0x04, 0xc5, 0x9e, 0xfa, 0x0d, 0xe2, 0xf7, 0xb8, 0x40, 0xf1, 0x45, 0xfc,
+  0xe6, 0xc2, 0xc9, 0x6f, 0xc7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0x7e, 0x30, 0x82, 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34,
+  0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24,
+  0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0xc3, 0xab, 0xa0, 0x02, 0xf0, 0x9b, 0xf5, 0x66, 0x7f, 0x28,
+  0x15, 0x92, 0x22, 0x95, 0xdb, 0xb8, 0x4e, 0xd3, 0x93, 0x08, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06,
+  0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x1b,
+  0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30,
+  0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+  0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81,
+  0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+  0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e,
+  0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75,
+  0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x66, 0x9c, 0x13, 0x6d,
+  0xd2, 0x7e, 0x2c, 0xdd, 0x6b, 0xd6, 0x1a, 0x91, 0x37, 0x85, 0x86, 0x51,
+  0x23, 0x4d, 0x64, 0x63, 0xf5, 0x5e, 0x83, 0x3e, 0x88, 0xfe, 0x6f, 0x67,
+  0x87, 0x0e, 0xca, 0x85, 0x6d, 0xbb, 0x3b, 0x3c, 0xd5, 0xad, 0xfc, 0xba,
+  0x4d, 0xba, 0x8b, 0xbb, 0xc8, 0xc1, 0xed, 0x2c, 0xd4, 0x6d, 0xcb, 0x10,
+  0xc2, 0x33, 0xe3, 0xe7, 0x66, 0x97, 0x8f, 0x2b, 0xe5, 0x8f, 0x81, 0x8f,
+  0xed, 0xbc, 0xdd, 0x87, 0xb5, 0xdb, 0xdc, 0x23, 0x5f, 0xae, 0x0f, 0x40,
+  0x91, 0x29, 0x9e, 0x07, 0xd4, 0xb1, 0xce, 0xd0, 0x82, 0x1b, 0x6e, 0x1d,
+  0xd1, 0xa4, 0x08, 0x50, 0x12, 0xae, 0x8f, 0x0f, 0x79, 0x67, 0xa7, 0x00,
+  0x67, 0xde, 0xba, 0x90, 0x9b, 0x48, 0xbc, 0x5f, 0x90, 0xc3, 0x1b, 0xfe,
+  0xcc, 0xb6, 0x3a, 0x1e, 0xdb, 0x15, 0x15, 0xb5, 0xde, 0xab, 0x78, 0xe3,
+  0x41, 0xaa, 0x93, 0x8a, 0xe1, 0xbf, 0x43, 0x15, 0xec, 0xc9, 0x6b, 0x21,
+  0xfe, 0xed, 0xa1, 0xdf, 0xe9, 0x0b, 0x2d, 0xcb, 0xa0, 0x73, 0x1f, 0xd6,
+  0x3e, 0xf8, 0x98, 0x9b, 0x46, 0x78, 0xe4, 0xad, 0x25, 0x20, 0x41, 0x86,
+  0x28, 0xd0, 0xde, 0x7d, 0x14, 0x96, 0x04, 0x47, 0xac, 0xc8, 0xb9, 0x6b,
+  0xdd, 0x00, 0xf0, 0x47, 0x11, 0x9f, 0x8b, 0x7e, 0xb1, 0xa2, 0xed, 0x47,
+  0xe9, 0x17, 0x23, 0x34, 0xe6, 0xbd, 0x8b, 0x67, 0x41, 0x64, 0x60, 0x0a,
+  0x1a, 0xcd, 0x75, 0x69, 0x89, 0x39, 0x66, 0x95, 0xe1, 0x32, 0x87, 0x73,
+  0x91, 0xd0, 0x9b, 0x83, 0x8d, 0xb8, 0xc7, 0xe0, 0xbc, 0x22, 0x8f, 0x2c,
+  0x24, 0x13, 0xc8, 0xc2, 0x94, 0x97, 0xfa, 0x31, 0x26, 0x22, 0x82, 0x2b,
+  0xb5, 0xef, 0x05, 0xa6, 0xa0, 0x7e, 0x9a, 0x00, 0xb4, 0x6b, 0xe3, 0x9e,
+  0x59, 0x43, 0xbc, 0x76, 0x98, 0xf3, 0x3c, 0x30, 0xdb, 0x1c, 0x30, 0x2e,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            7a:ac:a2:1d:53:9d:14:54:11:3c:04:5e:d8:35:f8:ea
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+        Validity
+            Not Before: Nov 26 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions EV Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d0:35:5c:e2:e7:95:1a:98:8f:d8:4f:d6:d5:dc:
+                    7e:5c:82:bf:9f:cc:4b:fa:3c:4a:81:bc:da:c5:a7:
+                    e9:ad:9a:26:8f:dc:19:2c:63:12:3e:56:df:75:e6:
+                    48:ac:e3:47:90:7f:5f:08:f1:a3:80:d1:d0:cd:25:
+                    cd:59:f3:ad:2e:c3:eb:06:09:fe:39:24:39:a2:a1:
+                    ec:c4:c4:9a:d7:a0:08:55:fe:c8:c5:64:2e:fc:e7:
+                    06:88:95:c1:3e:31:5a:55:f0:1d:98:04:94:b4:7f:
+                    5e:dc:90:a9:a1:85:c7:aa:12:b9:87:d1:a3:71:11:
+                    02:6c:7e:9b:c9:39:eb:ec:b5:58:27:8b:a3:98:11:
+                    a0:ab:83:fb:24:30:00:ae:02:57:fe:80:e2:ca:8f:
+                    48:60:63:39:db:af:96:74:83:bb:3b:6c:ef:b3:33:
+                    c6:a6:dc:31:e9:f9:bc:aa:b7:1e:c8:f4:7f:58:69:
+                    72:ee:5a:8f:36:0a:fe:32:11:1c:34:3d:79:88:69:
+                    d7:da:30:73:36:68:e1:fc:10:28:41:ee:6c:7f:88:
+                    08:3e:93:77:63:8a:aa:c8:a8:7b:cb:34:70:04:a1:
+                    6c:3b:6d:48:27:d4:3d:17:ba:0c:a3:e1:8a:5a:ab:
+                    1f:e1:72:26:c3:8e:26:32:28:d9:72:49:0e:ee:e5:
+                    75:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:21:30:C9:FB:00:D7:4E:98:DA:87:AA:2A:D0:A7:2E:B1:40:31:A7:4C
+
+            X509v3 Subject Key Identifier: 
+                8A:35:E4:35:3A:BC:11:A1:9E:FB:F5:4F:34:66:D5:4B:AC:4C:62:68
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.networksolutions.com/legal/SSL-legal-repository-ev-cps.jsp
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.netsolssl.com/NetworkSolutionsCertificateAuthority.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/NetworkSolutionsAddTrustEVServerCA.crt
+                OCSP - URI:http://ocsp.netsolssl.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3b:41:a7:b0:f6:24:18:e5:c8:77:0e:a8:05:bc:e8:48:57:ce:
+         81:23:ff:17:98:68:01:89:c5:69:9e:c2:ab:45:ab:73:4c:25:
+         c9:6f:77:05:72:10:eb:9e:5e:72:0c:f7:d3:7f:bc:63:1c:b0:
+         e5:4c:44:01:99:1f:e1:de:fc:70:e3:77:e5:d8:e9:a9:2d:95:
+         dd:05:cf:6e:c5:c7:d9:dc:2f:d1:40:7e:8f:e9:47:8b:87:d9:
+         81:33:a5:2b:4c:b9:2e:a4:e1:a8:cc:1c:6b:cf:04:36:5a:aa:
+         a4:a0:74:30:1b:51:20:c7:61:b9:50:18:e4:bf:2b:c3:f8:a6:
+         fa:8c:89:16:21:99:a7:5a:43:99:03:6d:74:e0:8b:ea:b0:78:
+         8e:20:01:d2:29:b2:8c:f1:7b:2a:08:b2:62:6a:30:36:5d:5c:
+         a7:3b:4a:ee:f7:07:32:47:2d:f6:88:62:0c:a9:24:e0:70:df:
+         a2:a6:42:0c:7b:7d:28:05:d7:0b:6d:e5:84:fb:f0:c9:88:b3:
+         a9:d9:01:c3:9c:98:dc:cb:83:47:ec:f9:d1:9e:a0:5c:5d:a7:
+         31:52:b8:5d:b0:91:03:6f:1e:6a:ef:e3:36:02:e3:1a:5d:31:
+         4a:90:16:1b:d7:33:05:30:fb:00:aa:28:eb:5f:0d:e7:14:56:
+         27:5d:7c:b4
+-----BEGIN CERTIFICATE-----
+MIIE8DCCA9igAwIBAgIQeqyiHVOdFFQRPARe2DX46jANBgkqhkiG9w0BAQUFADBi
+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkwHhcNMTAxMTI2MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBZMQswCQYDVQQGEwJV
+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMScwJQYDVQQDEx5O
+ZXR3b3JrIFNvbHV0aW9ucyBFViBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDQNVzi55UamI/YT9bV3H5cgr+fzEv6PEqBvNrFp+mtmiaP
+3BksYxI+Vt915kis40eQf18I8aOA0dDNJc1Z860uw+sGCf45JDmioezExJrXoAhV
+/sjFZC785waIlcE+MVpV8B2YBJS0f17ckKmhhceqErmH0aNxEQJsfpvJOevstVgn
+i6OYEaCrg/skMACuAlf+gOLKj0hgYznbr5Z0g7s7bO+zM8am3DHp+byqtx7I9H9Y
+aXLuWo82Cv4yERw0PXmIadfaMHM2aOH8EChB7mx/iAg+k3djiqrIqHvLNHAEoWw7
+bUgn1D0Xugyj4Ypaqx/hcibDjiYyKNlySQ7u5XVDAgMBAAGjggGpMIIBpTAfBgNV
+HSMEGDAWgBQhMMn7ANdOmNqHqirQpy6xQDGnTDAdBgNVHQ4EFgQUijXkNTq8EaGe
++/VPNGbVS6xMYmgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+ZgYDVR0gBF8wXTBbBgRVHSAAMFMwUQYIKwYBBQUHAgEWRWh0dHA6Ly93d3cubmV0
+d29ya3NvbHV0aW9ucy5jb20vbGVnYWwvU1NMLWxlZ2FsLXJlcG9zaXRvcnktZXYt
+Y3BzLmpzcDBSBgNVHR8ESzBJMEegRaBDhkFodHRwOi8vY3JsLm5ldHNvbHNzbC5j
+b20vTmV0d29ya1NvbHV0aW9uc0NlcnRpZmljYXRlQXV0aG9yaXR5LmNybDCBggYI
+KwYBBQUHAQEEdjB0MEsGCCsGAQUFBzAChj9odHRwOi8vY3J0LnVzZXJ0cnVzdC5j
+b20vTmV0d29ya1NvbHV0aW9uc0FkZFRydXN0RVZTZXJ2ZXJDQS5jcnQwJQYIKwYB
+BQUHMAGGGWh0dHA6Ly9vY3NwLm5ldHNvbHNzbC5jb20wDQYJKoZIhvcNAQEFBQAD
+ggEBADtBp7D2JBjlyHcOqAW86EhXzoEj/xeYaAGJxWmewqtFq3NMJclvdwVyEOue
+XnIM99N/vGMcsOVMRAGZH+He/HDjd+XY6aktld0Fz27Fx9ncL9FAfo/pR4uH2YEz
+pStMuS6k4ajMHGvPBDZaqqSgdDAbUSDHYblQGOS/K8P4pvqMiRYhmadaQ5kDbXTg
+i+qweI4gAdIpsozxeyoIsmJqMDZdXKc7Su73BzJHLfaIYgypJOBw36KmQgx7fSgF
+1wtt5YT78MmIs6nZAcOcmNzLg0fs+dGeoFxdpzFSuF2wkQNvHmrv4zYC4xpdMUqQ
+FhvXMwUw+wCqKOtfDecUViddfLQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert62[] = {
+  0x30, 0x82, 0x04, 0xf0, 0x30, 0x82, 0x03, 0xd8, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x7a, 0xac, 0xa2, 0x1d, 0x53, 0x9d, 0x14, 0x54, 0x11,
+  0x3c, 0x04, 0x5e, 0xd8, 0x35, 0xf8, 0xea, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x62,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+  0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+  0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+  0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x4e,
+  0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+  0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+  0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x31, 0x32, 0x36,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+  0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, 0x59,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+  0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+  0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+  0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x4e,
+  0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+  0x69, 0x6f, 0x6e, 0x73, 0x20, 0x45, 0x56, 0x20, 0x53, 0x65, 0x72, 0x76,
+  0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xd0, 0x35, 0x5c, 0xe2, 0xe7, 0x95, 0x1a, 0x98, 0x8f, 0xd8,
+  0x4f, 0xd6, 0xd5, 0xdc, 0x7e, 0x5c, 0x82, 0xbf, 0x9f, 0xcc, 0x4b, 0xfa,
+  0x3c, 0x4a, 0x81, 0xbc, 0xda, 0xc5, 0xa7, 0xe9, 0xad, 0x9a, 0x26, 0x8f,
+  0xdc, 0x19, 0x2c, 0x63, 0x12, 0x3e, 0x56, 0xdf, 0x75, 0xe6, 0x48, 0xac,
+  0xe3, 0x47, 0x90, 0x7f, 0x5f, 0x08, 0xf1, 0xa3, 0x80, 0xd1, 0xd0, 0xcd,
+  0x25, 0xcd, 0x59, 0xf3, 0xad, 0x2e, 0xc3, 0xeb, 0x06, 0x09, 0xfe, 0x39,
+  0x24, 0x39, 0xa2, 0xa1, 0xec, 0xc4, 0xc4, 0x9a, 0xd7, 0xa0, 0x08, 0x55,
+  0xfe, 0xc8, 0xc5, 0x64, 0x2e, 0xfc, 0xe7, 0x06, 0x88, 0x95, 0xc1, 0x3e,
+  0x31, 0x5a, 0x55, 0xf0, 0x1d, 0x98, 0x04, 0x94, 0xb4, 0x7f, 0x5e, 0xdc,
+  0x90, 0xa9, 0xa1, 0x85, 0xc7, 0xaa, 0x12, 0xb9, 0x87, 0xd1, 0xa3, 0x71,
+  0x11, 0x02, 0x6c, 0x7e, 0x9b, 0xc9, 0x39, 0xeb, 0xec, 0xb5, 0x58, 0x27,
+  0x8b, 0xa3, 0x98, 0x11, 0xa0, 0xab, 0x83, 0xfb, 0x24, 0x30, 0x00, 0xae,
+  0x02, 0x57, 0xfe, 0x80, 0xe2, 0xca, 0x8f, 0x48, 0x60, 0x63, 0x39, 0xdb,
+  0xaf, 0x96, 0x74, 0x83, 0xbb, 0x3b, 0x6c, 0xef, 0xb3, 0x33, 0xc6, 0xa6,
+  0xdc, 0x31, 0xe9, 0xf9, 0xbc, 0xaa, 0xb7, 0x1e, 0xc8, 0xf4, 0x7f, 0x58,
+  0x69, 0x72, 0xee, 0x5a, 0x8f, 0x36, 0x0a, 0xfe, 0x32, 0x11, 0x1c, 0x34,
+  0x3d, 0x79, 0x88, 0x69, 0xd7, 0xda, 0x30, 0x73, 0x36, 0x68, 0xe1, 0xfc,
+  0x10, 0x28, 0x41, 0xee, 0x6c, 0x7f, 0x88, 0x08, 0x3e, 0x93, 0x77, 0x63,
+  0x8a, 0xaa, 0xc8, 0xa8, 0x7b, 0xcb, 0x34, 0x70, 0x04, 0xa1, 0x6c, 0x3b,
+  0x6d, 0x48, 0x27, 0xd4, 0x3d, 0x17, 0xba, 0x0c, 0xa3, 0xe1, 0x8a, 0x5a,
+  0xab, 0x1f, 0xe1, 0x72, 0x26, 0xc3, 0x8e, 0x26, 0x32, 0x28, 0xd9, 0x72,
+  0x49, 0x0e, 0xee, 0xe5, 0x75, 0x43, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0xa9, 0x30, 0x82, 0x01, 0xa5, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x21, 0x30, 0xc9, 0xfb,
+  0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1,
+  0x40, 0x31, 0xa7, 0x4c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x8a, 0x35, 0xe4, 0x35, 0x3a, 0xbc, 0x11, 0xa1, 0x9e,
+  0xfb, 0xf5, 0x4f, 0x34, 0x66, 0xd5, 0x4b, 0xac, 0x4c, 0x62, 0x68, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x66, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x5f, 0x30, 0x5d, 0x30, 0x5b,
+  0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x53, 0x30, 0x51, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x45, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6e, 0x65, 0x74,
+  0x77, 0x6f, 0x72, 0x6b, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+  0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2f,
+  0x53, 0x53, 0x4c, 0x2d, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2d, 0x65, 0x76, 0x2d,
+  0x63, 0x70, 0x73, 0x2e, 0x6a, 0x73, 0x70, 0x30, 0x52, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x4b, 0x30, 0x49, 0x30, 0x47, 0xa0, 0x45, 0xa0, 0x43,
+  0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+  0x2e, 0x6e, 0x65, 0x74, 0x73, 0x6f, 0x6c, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x6f,
+  0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x82, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x76, 0x30, 0x74,
+  0x30, 0x4b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02,
+  0x86, 0x3f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74,
+  0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x6f,
+  0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x45, 0x56, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43,
+  0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x6e, 0x65, 0x74, 0x73, 0x6f,
+  0x6c, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x3b, 0x41, 0xa7, 0xb0, 0xf6, 0x24, 0x18, 0xe5,
+  0xc8, 0x77, 0x0e, 0xa8, 0x05, 0xbc, 0xe8, 0x48, 0x57, 0xce, 0x81, 0x23,
+  0xff, 0x17, 0x98, 0x68, 0x01, 0x89, 0xc5, 0x69, 0x9e, 0xc2, 0xab, 0x45,
+  0xab, 0x73, 0x4c, 0x25, 0xc9, 0x6f, 0x77, 0x05, 0x72, 0x10, 0xeb, 0x9e,
+  0x5e, 0x72, 0x0c, 0xf7, 0xd3, 0x7f, 0xbc, 0x63, 0x1c, 0xb0, 0xe5, 0x4c,
+  0x44, 0x01, 0x99, 0x1f, 0xe1, 0xde, 0xfc, 0x70, 0xe3, 0x77, 0xe5, 0xd8,
+  0xe9, 0xa9, 0x2d, 0x95, 0xdd, 0x05, 0xcf, 0x6e, 0xc5, 0xc7, 0xd9, 0xdc,
+  0x2f, 0xd1, 0x40, 0x7e, 0x8f, 0xe9, 0x47, 0x8b, 0x87, 0xd9, 0x81, 0x33,
+  0xa5, 0x2b, 0x4c, 0xb9, 0x2e, 0xa4, 0xe1, 0xa8, 0xcc, 0x1c, 0x6b, 0xcf,
+  0x04, 0x36, 0x5a, 0xaa, 0xa4, 0xa0, 0x74, 0x30, 0x1b, 0x51, 0x20, 0xc7,
+  0x61, 0xb9, 0x50, 0x18, 0xe4, 0xbf, 0x2b, 0xc3, 0xf8, 0xa6, 0xfa, 0x8c,
+  0x89, 0x16, 0x21, 0x99, 0xa7, 0x5a, 0x43, 0x99, 0x03, 0x6d, 0x74, 0xe0,
+  0x8b, 0xea, 0xb0, 0x78, 0x8e, 0x20, 0x01, 0xd2, 0x29, 0xb2, 0x8c, 0xf1,
+  0x7b, 0x2a, 0x08, 0xb2, 0x62, 0x6a, 0x30, 0x36, 0x5d, 0x5c, 0xa7, 0x3b,
+  0x4a, 0xee, 0xf7, 0x07, 0x32, 0x47, 0x2d, 0xf6, 0x88, 0x62, 0x0c, 0xa9,
+  0x24, 0xe0, 0x70, 0xdf, 0xa2, 0xa6, 0x42, 0x0c, 0x7b, 0x7d, 0x28, 0x05,
+  0xd7, 0x0b, 0x6d, 0xe5, 0x84, 0xfb, 0xf0, 0xc9, 0x88, 0xb3, 0xa9, 0xd9,
+  0x01, 0xc3, 0x9c, 0x98, 0xdc, 0xcb, 0x83, 0x47, 0xec, 0xf9, 0xd1, 0x9e,
+  0xa0, 0x5c, 0x5d, 0xa7, 0x31, 0x52, 0xb8, 0x5d, 0xb0, 0x91, 0x03, 0x6f,
+  0x1e, 0x6a, 0xef, 0xe3, 0x36, 0x02, 0xe3, 0x1a, 0x5d, 0x31, 0x4a, 0x90,
+  0x16, 0x1b, 0xd7, 0x33, 0x05, 0x30, 0xfb, 0x00, 0xaa, 0x28, 0xeb, 0x5f,
+  0x0d, 0xe7, 0x14, 0x56, 0x27, 0x5d, 0x7c, 0xb4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4b:75:57:82:69:39:0c:9b:e3:2f:12:ec:5f:6d:94:5e
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Feb 11 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+                    98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+                    e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+                    b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+                    86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+                    da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+                    d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+                    7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+                    9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+                    30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+                    77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+                    02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+                    c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+                    00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+                    0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+                    2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+                    47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+                    26:f7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         4d:87:0d:50:30:f3:82:5d:c4:3f:d4:ef:ee:8d:48:e3:e7:bd:
+         90:6b:c4:32:38:c6:5e:28:ab:5c:a5:ad:61:f9:8e:bb:85:14:
+         39:21:51:5b:8e:8c:dc:17:92:80:2f:83:94:69:88:c1:be:27:
+         8e:4f:9f:a9:83:d8:be:d7:87:92:71:a3:b6:fd:11:74:b8:95:
+         81:28:20:77:0d:43:77:75:76:38:1d:4d:1b:2e:97:89:8c:0a:
+         1b:66:16:52:d4:14:9a:6f:80:48:16:de:30:c0:42:68:ea:bf:
+         a2:ba:2a:44:4d:ac:89:e2:f3:cc:53:9b:e3:e6:1d:6e:4f:98:
+         9f:d9:0e:51:50:86:e0:1a:34:32:24:80:7d:3a:87:f3:3c:e5:
+         5a:4d:b7:8b:bd:0a:24:0d:ae:db:f4:8f:5c:d2:66:0c:82:1c:
+         72:37:b6:d1:b9:d0:98:34:1b:27:6d:8b:5e:1e:40:73:18:fa:
+         a8:e4:c6:e8:90:c3:ab:19:e4:c1:a1:cd:4c:d4:3a:b6:88:c8:
+         f3:d0:65:61:3a:bf:18:f4:af:1c:56:a9:eb:97:38:d9:20:29:
+         1f:3f:2a:29:47:9d:8a:0f:6a:12:81:44:02:21:d4:3b:3a:1a:
+         2b:1e:40:43:7d:94:a0:69:0e:fc:2e:fb:52:f6:fd:2e:32:d8:
+         cb:6b:bd:eb
+-----BEGIN CERTIFICATE-----
+MIIE8TCCA9mgAwIBAgIQS3VXgmk5DJvjLxLsX22UXjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDIxMTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMScwJQYD
+VQQDEx5DT01PRE8gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQQIuLcuORG/dRwRtUBJjTqb/B5opdO4f7u4jO
+DeMvPwaW8KIpUJmu2zuhV7B0UXHN7UKRTUH+qcjYaoZ3RLtZZpdQXrTULHBEz9o3
+lUJpPDDEcbNS8CFNodi6OXwcnqMknfKDFpiqFnxDmxVbt640kf7UYiYYRpo/68H5
+8ZBX66x6DYvbcjBqZtXgRqNw3GjZ/wRIiXfeten7Z21B6bw5vTLZYgLxsag9bjec
+4i/i06Imi8a4VUOI4SM+pdIkOWpHqwDUobOpJf4NP6cdutNRwQuk2qw471VQJAVl
+RpM0Ty2NrcbUIRnSjsoFYXEHc0flihkSvQRNzk6cpUisuyb3AgMBAAGjggF0MIIB
+cDAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUC1jl
+i8ZMFTekQKkwqSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYI
+KwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0
+cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUA
+A4IBAQBNhw1QMPOCXcQ/1O/ujUjj572Qa8QyOMZeKKtcpa1h+Y67hRQ5IVFbjozc
+F5KAL4OUaYjBvieOT5+pg9i+14eScaO2/RF0uJWBKCB3DUN3dXY4HU0bLpeJjAob
+ZhZS1BSab4BIFt4wwEJo6r+iuipETayJ4vPMU5vj5h1uT5if2Q5RUIbgGjQyJIB9
+OofzPOVaTbeLvQokDa7b9I9c0mYMghxyN7bRudCYNBsnbYteHkBzGPqo5MbokMOr
+GeTBoc1M1Dq2iMjz0GVhOr8Y9K8cVqnrlzjZICkfPyopR52KD2oSgUQCIdQ7Ohor
+HkBDfZSgaQ78LvtS9v0uMtjLa73r
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert63[] = {
+  0x30, 0x82, 0x04, 0xf1, 0x30, 0x82, 0x03, 0xd9, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x4b, 0x75, 0x57, 0x82, 0x69, 0x39, 0x0c, 0x9b, 0xe3,
+  0x2f, 0x12, 0xec, 0x5f, 0x6d, 0x94, 0x5e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x31,
+  0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+  0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+  0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+  0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b, 0x8b,
+  0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98, 0xd3,
+  0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88, 0xce,
+  0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99, 0xae,
+  0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42, 0x91,
+  0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb, 0x59,
+  0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda, 0x37,
+  0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21, 0x4d,
+  0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2, 0x83,
+  0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae, 0x34,
+  0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1, 0xf9,
+  0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30, 0x6a,
+  0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04, 0x48,
+  0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc, 0x39,
+  0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37, 0x9c,
+  0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43, 0x88,
+  0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00, 0xd4,
+  0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3, 0x51,
+  0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05, 0x65,
+  0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19, 0xd2,
+  0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19, 0x12,
+  0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26, 0xf7,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x74, 0x30, 0x82, 0x01,
+  0x70, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4,
+  0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58, 0xe5,
+  0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe,
+  0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+  0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+  0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+  0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+  0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+  0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x01, 0x00, 0x4d, 0x87, 0x0d, 0x50, 0x30, 0xf3, 0x82,
+  0x5d, 0xc4, 0x3f, 0xd4, 0xef, 0xee, 0x8d, 0x48, 0xe3, 0xe7, 0xbd, 0x90,
+  0x6b, 0xc4, 0x32, 0x38, 0xc6, 0x5e, 0x28, 0xab, 0x5c, 0xa5, 0xad, 0x61,
+  0xf9, 0x8e, 0xbb, 0x85, 0x14, 0x39, 0x21, 0x51, 0x5b, 0x8e, 0x8c, 0xdc,
+  0x17, 0x92, 0x80, 0x2f, 0x83, 0x94, 0x69, 0x88, 0xc1, 0xbe, 0x27, 0x8e,
+  0x4f, 0x9f, 0xa9, 0x83, 0xd8, 0xbe, 0xd7, 0x87, 0x92, 0x71, 0xa3, 0xb6,
+  0xfd, 0x11, 0x74, 0xb8, 0x95, 0x81, 0x28, 0x20, 0x77, 0x0d, 0x43, 0x77,
+  0x75, 0x76, 0x38, 0x1d, 0x4d, 0x1b, 0x2e, 0x97, 0x89, 0x8c, 0x0a, 0x1b,
+  0x66, 0x16, 0x52, 0xd4, 0x14, 0x9a, 0x6f, 0x80, 0x48, 0x16, 0xde, 0x30,
+  0xc0, 0x42, 0x68, 0xea, 0xbf, 0xa2, 0xba, 0x2a, 0x44, 0x4d, 0xac, 0x89,
+  0xe2, 0xf3, 0xcc, 0x53, 0x9b, 0xe3, 0xe6, 0x1d, 0x6e, 0x4f, 0x98, 0x9f,
+  0xd9, 0x0e, 0x51, 0x50, 0x86, 0xe0, 0x1a, 0x34, 0x32, 0x24, 0x80, 0x7d,
+  0x3a, 0x87, 0xf3, 0x3c, 0xe5, 0x5a, 0x4d, 0xb7, 0x8b, 0xbd, 0x0a, 0x24,
+  0x0d, 0xae, 0xdb, 0xf4, 0x8f, 0x5c, 0xd2, 0x66, 0x0c, 0x82, 0x1c, 0x72,
+  0x37, 0xb6, 0xd1, 0xb9, 0xd0, 0x98, 0x34, 0x1b, 0x27, 0x6d, 0x8b, 0x5e,
+  0x1e, 0x40, 0x73, 0x18, 0xfa, 0xa8, 0xe4, 0xc6, 0xe8, 0x90, 0xc3, 0xab,
+  0x19, 0xe4, 0xc1, 0xa1, 0xcd, 0x4c, 0xd4, 0x3a, 0xb6, 0x88, 0xc8, 0xf3,
+  0xd0, 0x65, 0x61, 0x3a, 0xbf, 0x18, 0xf4, 0xaf, 0x1c, 0x56, 0xa9, 0xeb,
+  0x97, 0x38, 0xd9, 0x20, 0x29, 0x1f, 0x3f, 0x2a, 0x29, 0x47, 0x9d, 0x8a,
+  0x0f, 0x6a, 0x12, 0x81, 0x44, 0x02, 0x21, 0xd4, 0x3b, 0x3a, 0x1a, 0x2b,
+  0x1e, 0x40, 0x43, 0x7d, 0x94, 0xa0, 0x69, 0x0e, 0xfc, 0x2e, 0xfb, 0x52,
+  0xf6, 0xfd, 0x2e, 0x32, 0xd8, 0xcb, 0x6b, 0xbd, 0xeb,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            6f:25:dc:15:af:df:5e:a3:08:56:0c:3b:7a:4f:c7:f8
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: May 30 10:48:38 2000 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+                    98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+                    e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+                    b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+                    86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+                    da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+                    d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+                    7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+                    9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+                    30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+                    77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+                    02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+                    c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+                    00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+                    0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+                    2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+                    47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+                    26:f7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         07:60:93:99:aa:ce:d0:d3:47:d0:37:33:de:3f:64:b7:e5:2e:
+         a3:25:0c:d5:33:1d:0d:8d:ab:f6:7e:46:7b:59:06:92:e3:82:
+         c4:e7:f5:f6:f3:d9:05:cf:49:34:2d:37:5f:f4:25:c7:f0:fb:
+         6b:23:77:f1:f1:40:d7:4c:bb:49:45:31:dd:00:28:67:b7:29:
+         4c:75:a8:1f:79:31:c9:36:37:0f:ca:35:4f:8c:f1:7e:de:fc:
+         46:ab:bf:68:9b:70:23:30:2e:b7:c5:5c:7b:8a:fb:18:13:79:
+         4b:92:42:8c:dc:2c:ab:6c:22:b7:28:53:b3:1a:4a:ce:1b:fb:
+         28:0e:b7:3a:a4:da:0d:f7:40:32:4f:df:6f:bb:01:50:fc:87:
+         d3:76:d9:fc:fb:b6:84:03:ca:c9:36:18:f7:dd:6c:db:bb:ba:
+         81:1c:a6:ad:fe:28:f9:cf:b9:a2:71:5d:19:05:ea:4a:46:dc:
+         73:41:ef:89:94:42:b1:43:88:6f:35:17:af:1e:60:83:ac:7a:
+         8c:10:7b:9f:c9:f6:83:6d:9e:fa:88:ee:3e:dd:ee:9e:b0:bf:
+         e0:6a:b9:d0:9f:07:b2:09:13:9a:f5:a4:e5:c8:5b:79:a7:47:
+         35:33:68:e5:55:9e:aa:5b:cb:30:0b:9d:c7:0f:bf:68:44:81:
+         97:8b:51:4a
+-----BEGIN CERTIFICATE-----
+MIIE8TCCA9mgAwIBAgIQbyXcFa/fXqMIVgw7ek/H+DANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMScwJQYD
+VQQDEx5DT01PRE8gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQQIuLcuORG/dRwRtUBJjTqb/B5opdO4f7u4jO
+DeMvPwaW8KIpUJmu2zuhV7B0UXHN7UKRTUH+qcjYaoZ3RLtZZpdQXrTULHBEz9o3
+lUJpPDDEcbNS8CFNodi6OXwcnqMknfKDFpiqFnxDmxVbt640kf7UYiYYRpo/68H5
+8ZBX66x6DYvbcjBqZtXgRqNw3GjZ/wRIiXfeten7Z21B6bw5vTLZYgLxsag9bjec
+4i/i06Imi8a4VUOI4SM+pdIkOWpHqwDUobOpJf4NP6cdutNRwQuk2qw471VQJAVl
+RpM0Ty2NrcbUIRnSjsoFYXEHc0flihkSvQRNzk6cpUisuyb3AgMBAAGjggF0MIIB
+cDAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUC1jl
+i8ZMFTekQKkwqSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYI
+KwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0
+cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUA
+A4IBAQAHYJOZqs7Q00fQNzPeP2S35S6jJQzVMx0Njav2fkZ7WQaS44LE5/X289kF
+z0k0LTdf9CXH8PtrI3fx8UDXTLtJRTHdAChntylMdagfeTHJNjcPyjVPjPF+3vxG
+q79om3AjMC63xVx7ivsYE3lLkkKM3CyrbCK3KFOzGkrOG/soDrc6pNoN90AyT99v
+uwFQ/IfTdtn8+7aEA8rJNhj33Wzbu7qBHKat/ij5z7micV0ZBepKRtxzQe+JlEKx
+Q4hvNRevHmCDrHqMEHufyfaDbZ76iO4+3e6esL/garnQnweyCROa9aTlyFt5p0c1
+M2jlVZ6qW8swC53HD79oRIGXi1FK
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert64[] = {
+  0x30, 0x82, 0x04, 0xf1, 0x30, 0x82, 0x03, 0xd9, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x6f, 0x25, 0xdc, 0x15, 0xaf, 0xdf, 0x5e, 0xa3, 0x08,
+  0x56, 0x0c, 0x3b, 0x7a, 0x4f, 0xc7, 0xf8, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x35, 0x33,
+  0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+  0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+  0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+  0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b, 0x8b,
+  0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98, 0xd3,
+  0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88, 0xce,
+  0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99, 0xae,
+  0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42, 0x91,
+  0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb, 0x59,
+  0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda, 0x37,
+  0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21, 0x4d,
+  0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2, 0x83,
+  0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae, 0x34,
+  0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1, 0xf9,
+  0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30, 0x6a,
+  0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04, 0x48,
+  0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc, 0x39,
+  0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37, 0x9c,
+  0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43, 0x88,
+  0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00, 0xd4,
+  0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3, 0x51,
+  0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05, 0x65,
+  0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19, 0xd2,
+  0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19, 0x12,
+  0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26, 0xf7,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x74, 0x30, 0x82, 0x01,
+  0x70, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4,
+  0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58, 0xe5,
+  0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe,
+  0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+  0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+  0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+  0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+  0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+  0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+  0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x01, 0x00, 0x07, 0x60, 0x93, 0x99, 0xaa, 0xce, 0xd0,
+  0xd3, 0x47, 0xd0, 0x37, 0x33, 0xde, 0x3f, 0x64, 0xb7, 0xe5, 0x2e, 0xa3,
+  0x25, 0x0c, 0xd5, 0x33, 0x1d, 0x0d, 0x8d, 0xab, 0xf6, 0x7e, 0x46, 0x7b,
+  0x59, 0x06, 0x92, 0xe3, 0x82, 0xc4, 0xe7, 0xf5, 0xf6, 0xf3, 0xd9, 0x05,
+  0xcf, 0x49, 0x34, 0x2d, 0x37, 0x5f, 0xf4, 0x25, 0xc7, 0xf0, 0xfb, 0x6b,
+  0x23, 0x77, 0xf1, 0xf1, 0x40, 0xd7, 0x4c, 0xbb, 0x49, 0x45, 0x31, 0xdd,
+  0x00, 0x28, 0x67, 0xb7, 0x29, 0x4c, 0x75, 0xa8, 0x1f, 0x79, 0x31, 0xc9,
+  0x36, 0x37, 0x0f, 0xca, 0x35, 0x4f, 0x8c, 0xf1, 0x7e, 0xde, 0xfc, 0x46,
+  0xab, 0xbf, 0x68, 0x9b, 0x70, 0x23, 0x30, 0x2e, 0xb7, 0xc5, 0x5c, 0x7b,
+  0x8a, 0xfb, 0x18, 0x13, 0x79, 0x4b, 0x92, 0x42, 0x8c, 0xdc, 0x2c, 0xab,
+  0x6c, 0x22, 0xb7, 0x28, 0x53, 0xb3, 0x1a, 0x4a, 0xce, 0x1b, 0xfb, 0x28,
+  0x0e, 0xb7, 0x3a, 0xa4, 0xda, 0x0d, 0xf7, 0x40, 0x32, 0x4f, 0xdf, 0x6f,
+  0xbb, 0x01, 0x50, 0xfc, 0x87, 0xd3, 0x76, 0xd9, 0xfc, 0xfb, 0xb6, 0x84,
+  0x03, 0xca, 0xc9, 0x36, 0x18, 0xf7, 0xdd, 0x6c, 0xdb, 0xbb, 0xba, 0x81,
+  0x1c, 0xa6, 0xad, 0xfe, 0x28, 0xf9, 0xcf, 0xb9, 0xa2, 0x71, 0x5d, 0x19,
+  0x05, 0xea, 0x4a, 0x46, 0xdc, 0x73, 0x41, 0xef, 0x89, 0x94, 0x42, 0xb1,
+  0x43, 0x88, 0x6f, 0x35, 0x17, 0xaf, 0x1e, 0x60, 0x83, 0xac, 0x7a, 0x8c,
+  0x10, 0x7b, 0x9f, 0xc9, 0xf6, 0x83, 0x6d, 0x9e, 0xfa, 0x88, 0xee, 0x3e,
+  0xdd, 0xee, 0x9e, 0xb0, 0xbf, 0xe0, 0x6a, 0xb9, 0xd0, 0x9f, 0x07, 0xb2,
+  0x09, 0x13, 0x9a, 0xf5, 0xa4, 0xe5, 0xc8, 0x5b, 0x79, 0xa7, 0x47, 0x35,
+  0x33, 0x68, 0xe5, 0x55, 0x9e, 0xaa, 0x5b, 0xcb, 0x30, 0x0b, 0x9d, 0xc7,
+  0x0f, 0xbf, 0x68, 0x44, 0x81, 0x97, 0x8b, 0x51, 0x4a,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 946072060 (0x3863e9fc)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+        Validity
+            Not Before: Dec 10 20:43:54 2009 GMT
+            Not After : Dec 10 21:13:54 2019 GMT
+        Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1C
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:97:a3:2d:3c:9e:de:05:da:13:c2:11:8d:9d:8e:
+                    e3:7f:c7:4b:7e:5a:9f:b3:ff:62:ab:73:c8:28:6b:
+                    ba:10:64:82:87:13:cd:57:18:ff:28:ce:c0:e6:0e:
+                    06:91:50:29:83:d1:f2:c3:2a:db:d8:db:4e:04:cc:
+                    00:eb:8b:b6:96:dc:bc:aa:fa:52:77:04:c1:db:19:
+                    e4:ae:9c:fd:3c:8b:03:ef:4d:bc:1a:03:65:f9:c1:
+                    b1:3f:72:86:f2:38:aa:19:ae:10:88:78:28:da:75:
+                    c3:3d:02:82:02:9c:b9:c1:65:77:76:24:4c:98:f7:
+                    6d:31:38:fb:db:fe:db:37:02:76:a1:18:97:a6:cc:
+                    de:20:09:49:36:24:69:42:f6:e4:37:62:f1:59:6d:
+                    a9:3c:ed:34:9c:a3:8e:db:dc:3a:d7:f7:0a:6f:ef:
+                    2e:d8:d5:93:5a:7a:ed:08:49:68:e2:41:e3:5a:90:
+                    c1:86:55:fc:51:43:9d:e0:b2:c4:67:b4:cb:32:31:
+                    25:f0:54:9f:4b:d1:6f:db:d4:dd:fc:af:5e:6c:78:
+                    90:95:de:ca:3a:48:b9:79:3c:9b:19:d6:75:05:a0:
+                    f9:88:d7:c1:e8:a5:09:e4:1a:15:dc:87:23:aa:b2:
+                    75:8c:63:25:87:d8:f8:3d:a6:c2:cc:66:ff:a5:66:
+                    68:55
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/2048ca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/rpa
+
+            X509v3 Subject Key Identifier: 
+                1E:F1:AB:89:06:F8:49:0F:01:33:77:EE:14:7A:EE:19:7C:93:28:4D
+            X509v3 Authority Key Identifier: 
+                keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+    Signature Algorithm: sha1WithRSAEncryption
+         07:f6:5f:82:84:7f:80:40:c7:90:34:46:42:24:03:ce:2f:ab:
+         ba:83:9e:25:73:0d:ed:ac:05:69:c6:87:ed:a3:5c:f2:57:c1:
+         b1:49:76:9a:4d:f2:3f:dd:e4:0e:fe:0b:3e:b9:98:d9:32:95:
+         1d:32:f4:01:ee:9c:c8:c8:e5:3f:e0:53:76:62:fc:dd:ab:6d:
+         3d:94:90:f2:c0:b3:3c:98:27:36:5e:28:97:22:fc:1b:40:d3:
+         2b:0d:ad:b5:57:6d:df:0f:e3:4b:ef:73:02:10:65:fa:1b:d0:
+         ac:31:d5:e3:0f:e8:ba:32:30:83:ee:4a:d0:bf:df:22:90:7a:
+         be:ec:3a:1b:c4:49:04:1d:f1:ae:80:77:3c:42:08:db:a7:3b:
+         28:a6:80:01:03:e6:39:a3:eb:df:80:59:1b:f3:2c:be:dc:72:
+         44:79:a0:6c:07:a5:6d:4d:44:8e:42:68:ca:94:7c:2e:36:ba:
+         85:9e:cd:aa:c4:5e:3c:54:be:fe:2f:ea:69:9d:1c:1e:29:9b:
+         96:d8:c8:fe:51:90:f1:24:a6:90:06:b3:f0:29:a2:ff:78:2e:
+         77:5c:45:21:d9:44:00:31:f3:be:32:4f:f5:0a:32:0d:fc:fc:
+         ba:16:76:56:b2:d6:48:92:f2:8b:a6:3e:b7:ac:5c:69:ea:0b:
+         3f:66:45:b9
+-----BEGIN CERTIFICATE-----
+MIIE8jCCA9qgAwIBAgIEOGPp/DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0wOTEyMTAyMDQzNTRaFw0xOTEy
+MTAyMTEzNTRaMIGxMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5j
+LjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L3JwYSBpcyBpbmNvcnBvcmF0ZWQg
+YnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwOSBFbnRydXN0LCBJbmMuMS4w
+LAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFDMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6MtPJ7eBdoTwhGNnY7jf8dL
+flqfs/9iq3PIKGu6EGSChxPNVxj/KM7A5g4GkVApg9Hywyrb2NtOBMwA64u2lty8
+qvpSdwTB2xnkrpz9PIsD7028GgNl+cGxP3KG8jiqGa4QiHgo2nXDPQKCApy5wWV3
+diRMmPdtMTj72/7bNwJ2oRiXpszeIAlJNiRpQvbkN2LxWW2pPO00nKOO29w61/cK
+b+8u2NWTWnrtCElo4kHjWpDBhlX8UUOd4LLEZ7TLMjEl8FSfS9Fv29Td/K9ebHiQ
+ld7KOki5eTybGdZ1BaD5iNfB6KUJ5BoV3IcjqrJ1jGMlh9j4PabCzGb/pWZoVQID
+AQABo4IBCzCCAQcwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wMwYI
+KwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0Lm5l
+dDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmVudHJ1c3QubmV0LzIwNDhj
+YS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93
+d3cuZW50cnVzdC5uZXQvcnBhMB0GA1UdDgQWBBQe8auJBvhJDwEzd+4Ueu4ZfJMo
+TTAfBgNVHSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDANBgkqhkiG9w0BAQUF
+AAOCAQEAB/ZfgoR/gEDHkDRGQiQDzi+ruoOeJXMN7awFacaH7aNc8lfBsUl2mk3y
+P93kDv4LPrmY2TKVHTL0Ae6cyMjlP+BTdmL83attPZSQ8sCzPJgnNl4olyL8G0DT
+Kw2ttVdt3w/jS+9zAhBl+hvQrDHV4w/oujIwg+5K0L/fIpB6vuw6G8RJBB3xroB3
+PEII26c7KKaAAQPmOaPr34BZG/MsvtxyRHmgbAelbU1EjkJoypR8Lja6hZ7NqsRe
+PFS+/i/qaZ0cHimbltjI/lGQ8SSmkAaz8Cmi/3gud1xFIdlEADHzvjJP9QoyDfz8
+uhZ2VrLWSJLyi6Y+t6xcaeoLP2ZFuQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert65[] = {
+  0x30, 0x82, 0x04, 0xf2, 0x30, 0x82, 0x03, 0xda, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x38, 0x63, 0xe9, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+  0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+  0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+  0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+  0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+  0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+  0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+  0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+  0x29, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32,
+  0x30, 0x34, 0x33, 0x35, 0x34, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32,
+  0x31, 0x30, 0x32, 0x31, 0x31, 0x33, 0x35, 0x34, 0x5a, 0x30, 0x81, 0xb1,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+  0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+  0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69,
+  0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20,
+  0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30,
+  0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x43, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde,
+  0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b,
+  0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba,
+  0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0,
+  0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb,
+  0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc,
+  0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd,
+  0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1,
+  0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28,
+  0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77,
+  0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb,
+  0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49,
+  0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9,
+  0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a,
+  0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68,
+  0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d,
+  0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f,
+  0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90,
+  0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75,
+  0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15,
+  0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8,
+  0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0b, 0x30, 0x82, 0x01, 0x07, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25,
+  0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+  0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29,
+  0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30, 0x34, 0x38, 0x63,
+  0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+  0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x1e, 0xf1, 0xab, 0x89, 0x06, 0xf8, 0x49,
+  0x0f, 0x01, 0x33, 0x77, 0xee, 0x14, 0x7a, 0xee, 0x19, 0x7c, 0x93, 0x28,
+  0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9,
+  0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9, 0x70, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x07, 0xf6, 0x5f, 0x82, 0x84, 0x7f,
+  0x80, 0x40, 0xc7, 0x90, 0x34, 0x46, 0x42, 0x24, 0x03, 0xce, 0x2f, 0xab,
+  0xba, 0x83, 0x9e, 0x25, 0x73, 0x0d, 0xed, 0xac, 0x05, 0x69, 0xc6, 0x87,
+  0xed, 0xa3, 0x5c, 0xf2, 0x57, 0xc1, 0xb1, 0x49, 0x76, 0x9a, 0x4d, 0xf2,
+  0x3f, 0xdd, 0xe4, 0x0e, 0xfe, 0x0b, 0x3e, 0xb9, 0x98, 0xd9, 0x32, 0x95,
+  0x1d, 0x32, 0xf4, 0x01, 0xee, 0x9c, 0xc8, 0xc8, 0xe5, 0x3f, 0xe0, 0x53,
+  0x76, 0x62, 0xfc, 0xdd, 0xab, 0x6d, 0x3d, 0x94, 0x90, 0xf2, 0xc0, 0xb3,
+  0x3c, 0x98, 0x27, 0x36, 0x5e, 0x28, 0x97, 0x22, 0xfc, 0x1b, 0x40, 0xd3,
+  0x2b, 0x0d, 0xad, 0xb5, 0x57, 0x6d, 0xdf, 0x0f, 0xe3, 0x4b, 0xef, 0x73,
+  0x02, 0x10, 0x65, 0xfa, 0x1b, 0xd0, 0xac, 0x31, 0xd5, 0xe3, 0x0f, 0xe8,
+  0xba, 0x32, 0x30, 0x83, 0xee, 0x4a, 0xd0, 0xbf, 0xdf, 0x22, 0x90, 0x7a,
+  0xbe, 0xec, 0x3a, 0x1b, 0xc4, 0x49, 0x04, 0x1d, 0xf1, 0xae, 0x80, 0x77,
+  0x3c, 0x42, 0x08, 0xdb, 0xa7, 0x3b, 0x28, 0xa6, 0x80, 0x01, 0x03, 0xe6,
+  0x39, 0xa3, 0xeb, 0xdf, 0x80, 0x59, 0x1b, 0xf3, 0x2c, 0xbe, 0xdc, 0x72,
+  0x44, 0x79, 0xa0, 0x6c, 0x07, 0xa5, 0x6d, 0x4d, 0x44, 0x8e, 0x42, 0x68,
+  0xca, 0x94, 0x7c, 0x2e, 0x36, 0xba, 0x85, 0x9e, 0xcd, 0xaa, 0xc4, 0x5e,
+  0x3c, 0x54, 0xbe, 0xfe, 0x2f, 0xea, 0x69, 0x9d, 0x1c, 0x1e, 0x29, 0x9b,
+  0x96, 0xd8, 0xc8, 0xfe, 0x51, 0x90, 0xf1, 0x24, 0xa6, 0x90, 0x06, 0xb3,
+  0xf0, 0x29, 0xa2, 0xff, 0x78, 0x2e, 0x77, 0x5c, 0x45, 0x21, 0xd9, 0x44,
+  0x00, 0x31, 0xf3, 0xbe, 0x32, 0x4f, 0xf5, 0x0a, 0x32, 0x0d, 0xfc, 0xfc,
+  0xba, 0x16, 0x76, 0x56, 0xb2, 0xd6, 0x48, 0x92, 0xf2, 0x8b, 0xa6, 0x3e,
+  0xb7, 0xac, 0x5c, 0x69, 0xea, 0x0b, 0x3f, 0x66, 0x45, 0xb9,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1276021817 (0x4c0e8c39)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+        Validity
+            Not Before: Nov 11 15:40:40 2011 GMT
+            Not After : Nov 12 02:51:17 2021 GMT
+        Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1C
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:97:a3:2d:3c:9e:de:05:da:13:c2:11:8d:9d:8e:
+                    e3:7f:c7:4b:7e:5a:9f:b3:ff:62:ab:73:c8:28:6b:
+                    ba:10:64:82:87:13:cd:57:18:ff:28:ce:c0:e6:0e:
+                    06:91:50:29:83:d1:f2:c3:2a:db:d8:db:4e:04:cc:
+                    00:eb:8b:b6:96:dc:bc:aa:fa:52:77:04:c1:db:19:
+                    e4:ae:9c:fd:3c:8b:03:ef:4d:bc:1a:03:65:f9:c1:
+                    b1:3f:72:86:f2:38:aa:19:ae:10:88:78:28:da:75:
+                    c3:3d:02:82:02:9c:b9:c1:65:77:76:24:4c:98:f7:
+                    6d:31:38:fb:db:fe:db:37:02:76:a1:18:97:a6:cc:
+                    de:20:09:49:36:24:69:42:f6:e4:37:62:f1:59:6d:
+                    a9:3c:ed:34:9c:a3:8e:db:dc:3a:d7:f7:0a:6f:ef:
+                    2e:d8:d5:93:5a:7a:ed:08:49:68:e2:41:e3:5a:90:
+                    c1:86:55:fc:51:43:9d:e0:b2:c4:67:b4:cb:32:31:
+                    25:f0:54:9f:4b:d1:6f:db:d4:dd:fc:af:5e:6c:78:
+                    90:95:de:ca:3a:48:b9:79:3c:9b:19:d6:75:05:a0:
+                    f9:88:d7:c1:e8:a5:09:e4:1a:15:dc:87:23:aa:b2:
+                    75:8c:63:25:87:d8:f8:3d:a6:c2:cc:66:ff:a5:66:
+                    68:55
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/2048ca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/rpa
+
+            X509v3 Subject Key Identifier: 
+                1E:F1:AB:89:06:F8:49:0F:01:33:77:EE:14:7A:EE:19:7C:93:28:4D
+            X509v3 Authority Key Identifier: 
+                keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+    Signature Algorithm: sha1WithRSAEncryption
+         40:9a:87:7e:88:d4:cc:26:a7:4b:fa:78:4a:20:d5:f9:a2:36:
+         21:bb:ee:5b:a0:4f:44:8d:cf:aa:f9:97:17:96:84:a9:c8:67:
+         9b:bb:e6:10:de:79:d6:56:6a:a4:78:14:49:d9:7c:ed:30:5e:
+         69:ea:6d:24:46:5a:88:34:3d:26:27:cf:69:41:84:1c:04:da:
+         19:38:2e:db:89:41:39:7e:65:1f:9d:5a:3a:cc:e1:0c:4c:37:
+         a1:ce:60:93:a8:b5:8c:ca:3f:ba:2b:5d:4c:1b:81:89:7a:ca:
+         36:30:9c:ff:84:e3:fe:3a:f1:f7:79:71:c9:b5:d3:33:03:ca:
+         77:ce:b0:ba:29:d2:34:5d:73:ff:a4:fd:f2:25:b8:35:45:79:
+         7a:1f:97:ae:c9:be:0a:68:84:99:74:39:a8:4e:7a:26:f5:cd:
+         de:25:e2:37:85:65:07:a7:ca:c5:05:b7:13:38:0d:2d:f0:6d:
+         19:ce:de:99:61:27:ee:45:6e:c7:39:ff:f6:c5:8b:e0:cb:7c:
+         8a:1e:d5:7a:07:31:2a:52:5c:3a:50:19:38:a9:44:fa:3c:a8:
+         cf:ef:79:9d:6a:d9:e5:2e:a1:8f:29:28:d7:ec:aa:c1:fb:26:
+         e6:9f:46:24:a6:b1:07:cd:b9:0c:e8:0d:82:16:00:1d:96:92:
+         fc:a6:08:a0
+-----BEGIN CERTIFICATE-----
+MIIE9TCCA92gAwIBAgIETA6MOTANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0xMTExMTExNTQwNDBaFw0yMTEx
+MTIwMjUxMTdaMIGxMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5j
+LjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L3JwYSBpcyBpbmNvcnBvcmF0ZWQg
+YnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwOSBFbnRydXN0LCBJbmMuMS4w
+LAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFDMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6MtPJ7eBdoTwhGNnY7jf8dL
+flqfs/9iq3PIKGu6EGSChxPNVxj/KM7A5g4GkVApg9Hywyrb2NtOBMwA64u2lty8
+qvpSdwTB2xnkrpz9PIsD7028GgNl+cGxP3KG8jiqGa4QiHgo2nXDPQKCApy5wWV3
+diRMmPdtMTj72/7bNwJ2oRiXpszeIAlJNiRpQvbkN2LxWW2pPO00nKOO29w61/cK
+b+8u2NWTWnrtCElo4kHjWpDBhlX8UUOd4LLEZ7TLMjEl8FSfS9Fv29Td/K9ebHiQ
+ld7KOki5eTybGdZ1BaD5iNfB6KUJ5BoV3IcjqrJ1jGMlh9j4PabCzGb/pWZoVQID
+AQABo4IBDjCCAQowDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+MwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0
+Lm5ldDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmVudHJ1c3QubmV0LzIw
+NDhjYS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6
+Ly93d3cuZW50cnVzdC5uZXQvcnBhMB0GA1UdDgQWBBQe8auJBvhJDwEzd+4Ueu4Z
+fJMoTTAfBgNVHSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDANBgkqhkiG9w0B
+AQUFAAOCAQEAQJqHfojUzCanS/p4SiDV+aI2IbvuW6BPRI3PqvmXF5aEqchnm7vm
+EN551lZqpHgUSdl87TBeaeptJEZaiDQ9JifPaUGEHATaGTgu24lBOX5lH51aOszh
+DEw3oc5gk6i1jMo/uitdTBuBiXrKNjCc/4Tj/jrx93lxybXTMwPKd86wuinSNF1z
+/6T98iW4NUV5eh+Xrsm+CmiEmXQ5qE56JvXN3iXiN4VlB6fKxQW3EzgNLfBtGc7e
+mWEn7kVuxzn/9sWL4Mt8ih7VegcxKlJcOlAZOKlE+jyoz+95nWrZ5S6hjyko1+yq
+wfsm5p9GJKaxB825DOgNghYAHZaS/KYIoA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert66[] = {
+  0x30, 0x82, 0x04, 0xf5, 0x30, 0x82, 0x03, 0xdd, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x4c, 0x0e, 0x8c, 0x39, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+  0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+  0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+  0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+  0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+  0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+  0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+  0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+  0x29, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+  0x35, 0x34, 0x30, 0x34, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31,
+  0x31, 0x32, 0x30, 0x32, 0x35, 0x31, 0x31, 0x37, 0x5a, 0x30, 0x81, 0xb1,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+  0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+  0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69,
+  0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20,
+  0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30,
+  0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x43, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde,
+  0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b,
+  0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba,
+  0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0,
+  0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb,
+  0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc,
+  0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd,
+  0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1,
+  0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28,
+  0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77,
+  0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb,
+  0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49,
+  0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9,
+  0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a,
+  0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68,
+  0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d,
+  0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f,
+  0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90,
+  0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75,
+  0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15,
+  0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8,
+  0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0e, 0x30, 0x82, 0x01, 0x0a, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+  0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30,
+  0x34, 0x38, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x1e, 0xf1, 0xab, 0x89,
+  0x06, 0xf8, 0x49, 0x0f, 0x01, 0x33, 0x77, 0xee, 0x14, 0x7a, 0xee, 0x19,
+  0x7c, 0x93, 0x28, 0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe,
+  0xd8, 0x89, 0xb9, 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9,
+  0x70, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x40, 0x9a, 0x87,
+  0x7e, 0x88, 0xd4, 0xcc, 0x26, 0xa7, 0x4b, 0xfa, 0x78, 0x4a, 0x20, 0xd5,
+  0xf9, 0xa2, 0x36, 0x21, 0xbb, 0xee, 0x5b, 0xa0, 0x4f, 0x44, 0x8d, 0xcf,
+  0xaa, 0xf9, 0x97, 0x17, 0x96, 0x84, 0xa9, 0xc8, 0x67, 0x9b, 0xbb, 0xe6,
+  0x10, 0xde, 0x79, 0xd6, 0x56, 0x6a, 0xa4, 0x78, 0x14, 0x49, 0xd9, 0x7c,
+  0xed, 0x30, 0x5e, 0x69, 0xea, 0x6d, 0x24, 0x46, 0x5a, 0x88, 0x34, 0x3d,
+  0x26, 0x27, 0xcf, 0x69, 0x41, 0x84, 0x1c, 0x04, 0xda, 0x19, 0x38, 0x2e,
+  0xdb, 0x89, 0x41, 0x39, 0x7e, 0x65, 0x1f, 0x9d, 0x5a, 0x3a, 0xcc, 0xe1,
+  0x0c, 0x4c, 0x37, 0xa1, 0xce, 0x60, 0x93, 0xa8, 0xb5, 0x8c, 0xca, 0x3f,
+  0xba, 0x2b, 0x5d, 0x4c, 0x1b, 0x81, 0x89, 0x7a, 0xca, 0x36, 0x30, 0x9c,
+  0xff, 0x84, 0xe3, 0xfe, 0x3a, 0xf1, 0xf7, 0x79, 0x71, 0xc9, 0xb5, 0xd3,
+  0x33, 0x03, 0xca, 0x77, 0xce, 0xb0, 0xba, 0x29, 0xd2, 0x34, 0x5d, 0x73,
+  0xff, 0xa4, 0xfd, 0xf2, 0x25, 0xb8, 0x35, 0x45, 0x79, 0x7a, 0x1f, 0x97,
+  0xae, 0xc9, 0xbe, 0x0a, 0x68, 0x84, 0x99, 0x74, 0x39, 0xa8, 0x4e, 0x7a,
+  0x26, 0xf5, 0xcd, 0xde, 0x25, 0xe2, 0x37, 0x85, 0x65, 0x07, 0xa7, 0xca,
+  0xc5, 0x05, 0xb7, 0x13, 0x38, 0x0d, 0x2d, 0xf0, 0x6d, 0x19, 0xce, 0xde,
+  0x99, 0x61, 0x27, 0xee, 0x45, 0x6e, 0xc7, 0x39, 0xff, 0xf6, 0xc5, 0x8b,
+  0xe0, 0xcb, 0x7c, 0x8a, 0x1e, 0xd5, 0x7a, 0x07, 0x31, 0x2a, 0x52, 0x5c,
+  0x3a, 0x50, 0x19, 0x38, 0xa9, 0x44, 0xfa, 0x3c, 0xa8, 0xcf, 0xef, 0x79,
+  0x9d, 0x6a, 0xd9, 0xe5, 0x2e, 0xa1, 0x8f, 0x29, 0x28, 0xd7, 0xec, 0xaa,
+  0xc1, 0xfb, 0x26, 0xe6, 0x9f, 0x46, 0x24, 0xa6, 0xb1, 0x07, 0xcd, 0xb9,
+  0x0c, 0xe8, 0x0d, 0x82, 0x16, 0x00, 0x1d, 0x96, 0x92, 0xfc, 0xa6, 0x08,
+  0xa0,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            40:89:95:44:7e:5f:b1:19:d8:65:73:70:2f:8d:64:fc
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Validity
+            Not Before: Dec  1 00:00:00 2006 GMT
+            Not After : Dec 31 23:59:59 2019 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=EssentialSSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ad:f0:08:b0:72:c6:ab:83:12:31:17:70:89:85:
+                    a9:20:12:d4:98:6a:ed:80:d4:d1:df:e4:8e:59:2d:
+                    d3:96:21:8d:76:d2:3f:18:0b:46:19:63:0b:c7:20:
+                    f3:e5:0b:dd:80:1a:f1:5a:a0:bd:1d:76:cd:b7:23:
+                    3a:74:5e:61:1b:75:aa:9b:d4:85:f4:e1:78:91:d3:
+                    2d:e1:af:fc:98:2e:06:d2:79:3d:5a:c0:1f:21:2d:
+                    1c:ae:21:53:c6:3a:a7:21:7e:be:ed:67:6f:75:1d:
+                    1a:9f:6a:5b:06:b3:6a:e3:b1:0b:aa:6a:0e:e7:6d:
+                    6c:c3:ca:95:8c:37:ce:21:1f:35:90:7d:db:da:1a:
+                    5c:a8:88:14:b2:0f:c8:12:20:5f:c5:d3:7f:e8:e1:
+                    38:e0:db:bc:f9:1f:a1:aa:d6:1b:90:07:21:fa:45:
+                    24:50:5d:27:2a:a0:28:41:45:5b:7d:bc:a0:a2:2f:
+                    aa:9b:7e:5b:53:c5:f1:05:16:57:7e:11:d7:3b:b4:
+                    d9:01:76:dc:df:7d:10:cf:51:a9:e5:38:f2:7b:14:
+                    00:75:59:f9:f0:59:db:17:3e:f7:af:e6:02:2d:a4:
+                    79:c1:5d:a2:1c:c3:9a:c8:a7:a8:0b:48:0a:6a:2e:
+                    7f:2d:97:65:f6:c5:04:9c:44:c8:99:96:7e:7e:a4:
+                    dd:2f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+            X509v3 Subject Key Identifier: 
+                DA:CB:EA:AD:5B:08:5D:CC:FF:FC:26:54:CE:49:E5:55:C6:38:F4:F8
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://secure.comodo.net/CPS
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/ComodoUTNServerCA.crt
+                CA Issuers - URI:http://crt.comodo.net/ComodoUTNServerCA.crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         6d:c3:6b:56:47:47:6b:3d:36:c2:db:51:ca:75:d0:5a:aa:62:
+         40:42:07:61:d8:fe:6b:2c:d8:03:e1:54:f6:9e:5c:16:e3:17:
+         37:ec:a0:f6:2b:7a:5f:2e:c3:50:01:d7:33:0e:0a:3e:ad:b3:
+         33:c7:4f:9e:45:26:c8:f1:ee:bd:64:62:ff:88:1f:c4:59:f7:
+         92:15:c8:e7:f7:38:ab:1f:00:ec:4c:f1:27:aa:01:0d:34:c7:
+         04:5a:b4:79:b2:9c:e4:31:61:ef:9a:12:33:69:d4:e0:30:6e:
+         1c:67:5d:f9:68:d6:31:37:6a:49:c5:0b:75:99:54:65:1e:5f:
+         2d:99:cb:a7:41:a4:fa:d2:b5:f0:d4:1e:48:ec:90:3f:d3:7d:
+         b1:ff:23:96:6b:23:35:b0:ed:9e:5f:3d:31:74:48:80:7d:90:
+         56:6d:10:fe:63:7c:ee:9a:d3:fd:9f:5f:21:09:0d:5e:cc:b3:
+         8d:5a:8f:d8:a0:41:35:a3:86:73:05:ae:d9:19:7a:3a:cb:20:
+         af:51:91:a3:cc:46:4d:47:50:c6:fb:dc:15:2c:54:71:bf:fe:
+         57:fb:89:ac:ff:d0:bb:8f:66:3e:ef:e4:21:af:80:47:ff:86:
+         db:39:11:c8:e6:50:cd:45:6d:59:96:ca:55:76:6d:b5:8e:b0:
+         de:09:68:00
+-----BEGIN CERTIFICATE-----
+MIIE+DCCA+CgAwIBAgIQQImVRH5fsRnYZXNwL41k/DANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh
+dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E
+TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf
+5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR
+0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD
+ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq
+oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX
+Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggF4
+MIIBdDAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU
+2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwPgYDVR0gBDcwNTAzBgRVHSAAMCswKQYIKwYBBQUHAgEWHWh0dHBz
+Oi8vc2VjdXJlLmNvbW9kby5uZXQvQ1BTMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6
+Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET0NlcnRpZmljYXRpb25BdXRob3JpdHku
+Y3JsMIGCBggrBgEFBQcBAQR2MHQwOQYIKwYBBQUHMAKGLWh0dHA6Ly9jcnQuY29t
+b2RvY2EuY29tL0NvbW9kb1VUTlNlcnZlckNBLmNydDA3BggrBgEFBQcwAoYraHR0
+cDovL2NydC5jb21vZG8ubmV0L0NvbW9kb1VUTlNlcnZlckNBLmNydDANBgkqhkiG
+9w0BAQUFAAOCAQEAbcNrVkdHaz02wttRynXQWqpiQEIHYdj+ayzYA+FU9p5cFuMX
+N+yg9it6Xy7DUAHXMw4KPq2zM8dPnkUmyPHuvWRi/4gfxFn3khXI5/c4qx8A7Ezx
+J6oBDTTHBFq0ebKc5DFh75oSM2nU4DBuHGdd+WjWMTdqScULdZlUZR5fLZnLp0Gk
++tK18NQeSOyQP9N9sf8jlmsjNbDtnl89MXRIgH2QVm0Q/mN87prT/Z9fIQkNXsyz
+jVqP2KBBNaOGcwWu2Rl6Ossgr1GRo8xGTUdQxvvcFSxUcb/+V/uJrP/Qu49mPu/k
+Ia+AR/+G2zkRyOZQzUVtWZbKVXZttY6w3gloAA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert67[] = {
+  0x30, 0x82, 0x04, 0xf8, 0x30, 0x82, 0x03, 0xe0, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x40, 0x89, 0x95, 0x44, 0x7e, 0x5f, 0xb1, 0x19, 0xd8,
+  0x65, 0x73, 0x70, 0x2f, 0x8d, 0x64, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+  0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x72, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30,
+  0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61,
+  0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74,
+  0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
+  0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+  0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
+  0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0f, 0x45,
+  0x73, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xad,
+  0xf0, 0x08, 0xb0, 0x72, 0xc6, 0xab, 0x83, 0x12, 0x31, 0x17, 0x70, 0x89,
+  0x85, 0xa9, 0x20, 0x12, 0xd4, 0x98, 0x6a, 0xed, 0x80, 0xd4, 0xd1, 0xdf,
+  0xe4, 0x8e, 0x59, 0x2d, 0xd3, 0x96, 0x21, 0x8d, 0x76, 0xd2, 0x3f, 0x18,
+  0x0b, 0x46, 0x19, 0x63, 0x0b, 0xc7, 0x20, 0xf3, 0xe5, 0x0b, 0xdd, 0x80,
+  0x1a, 0xf1, 0x5a, 0xa0, 0xbd, 0x1d, 0x76, 0xcd, 0xb7, 0x23, 0x3a, 0x74,
+  0x5e, 0x61, 0x1b, 0x75, 0xaa, 0x9b, 0xd4, 0x85, 0xf4, 0xe1, 0x78, 0x91,
+  0xd3, 0x2d, 0xe1, 0xaf, 0xfc, 0x98, 0x2e, 0x06, 0xd2, 0x79, 0x3d, 0x5a,
+  0xc0, 0x1f, 0x21, 0x2d, 0x1c, 0xae, 0x21, 0x53, 0xc6, 0x3a, 0xa7, 0x21,
+  0x7e, 0xbe, 0xed, 0x67, 0x6f, 0x75, 0x1d, 0x1a, 0x9f, 0x6a, 0x5b, 0x06,
+  0xb3, 0x6a, 0xe3, 0xb1, 0x0b, 0xaa, 0x6a, 0x0e, 0xe7, 0x6d, 0x6c, 0xc3,
+  0xca, 0x95, 0x8c, 0x37, 0xce, 0x21, 0x1f, 0x35, 0x90, 0x7d, 0xdb, 0xda,
+  0x1a, 0x5c, 0xa8, 0x88, 0x14, 0xb2, 0x0f, 0xc8, 0x12, 0x20, 0x5f, 0xc5,
+  0xd3, 0x7f, 0xe8, 0xe1, 0x38, 0xe0, 0xdb, 0xbc, 0xf9, 0x1f, 0xa1, 0xaa,
+  0xd6, 0x1b, 0x90, 0x07, 0x21, 0xfa, 0x45, 0x24, 0x50, 0x5d, 0x27, 0x2a,
+  0xa0, 0x28, 0x41, 0x45, 0x5b, 0x7d, 0xbc, 0xa0, 0xa2, 0x2f, 0xaa, 0x9b,
+  0x7e, 0x5b, 0x53, 0xc5, 0xf1, 0x05, 0x16, 0x57, 0x7e, 0x11, 0xd7, 0x3b,
+  0xb4, 0xd9, 0x01, 0x76, 0xdc, 0xdf, 0x7d, 0x10, 0xcf, 0x51, 0xa9, 0xe5,
+  0x38, 0xf2, 0x7b, 0x14, 0x00, 0x75, 0x59, 0xf9, 0xf0, 0x59, 0xdb, 0x17,
+  0x3e, 0xf7, 0xaf, 0xe6, 0x02, 0x2d, 0xa4, 0x79, 0xc1, 0x5d, 0xa2, 0x1c,
+  0xc3, 0x9a, 0xc8, 0xa7, 0xa8, 0x0b, 0x48, 0x0a, 0x6a, 0x2e, 0x7f, 0x2d,
+  0x97, 0x65, 0xf6, 0xc5, 0x04, 0x9c, 0x44, 0xc8, 0x99, 0x96, 0x7e, 0x7e,
+  0xa4, 0xdd, 0x2f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x78,
+  0x30, 0x82, 0x01, 0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+  0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+  0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0xda, 0xcb, 0xea, 0xad, 0x5b, 0x08, 0x5d, 0xcc, 0xff, 0xfc, 0x26, 0x54,
+  0xce, 0x49, 0xe5, 0x55, 0xc6, 0x38, 0xf4, 0xf8, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3e, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55,
+  0x1d, 0x20, 0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f,
+  0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53,
+  0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30,
+  0x3e, 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+  0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+  0x4f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x81, 0x82, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x76, 0x30, 0x74, 0x30, 0x39, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f,
+  0x6d, 0x6f, 0x64, 0x6f, 0x55, 0x54, 0x4e, 0x53, 0x65, 0x72, 0x76, 0x65,
+  0x72, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x37, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2b, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+  0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+  0x6f, 0x55, 0x54, 0x4e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00,
+  0x6d, 0xc3, 0x6b, 0x56, 0x47, 0x47, 0x6b, 0x3d, 0x36, 0xc2, 0xdb, 0x51,
+  0xca, 0x75, 0xd0, 0x5a, 0xaa, 0x62, 0x40, 0x42, 0x07, 0x61, 0xd8, 0xfe,
+  0x6b, 0x2c, 0xd8, 0x03, 0xe1, 0x54, 0xf6, 0x9e, 0x5c, 0x16, 0xe3, 0x17,
+  0x37, 0xec, 0xa0, 0xf6, 0x2b, 0x7a, 0x5f, 0x2e, 0xc3, 0x50, 0x01, 0xd7,
+  0x33, 0x0e, 0x0a, 0x3e, 0xad, 0xb3, 0x33, 0xc7, 0x4f, 0x9e, 0x45, 0x26,
+  0xc8, 0xf1, 0xee, 0xbd, 0x64, 0x62, 0xff, 0x88, 0x1f, 0xc4, 0x59, 0xf7,
+  0x92, 0x15, 0xc8, 0xe7, 0xf7, 0x38, 0xab, 0x1f, 0x00, 0xec, 0x4c, 0xf1,
+  0x27, 0xaa, 0x01, 0x0d, 0x34, 0xc7, 0x04, 0x5a, 0xb4, 0x79, 0xb2, 0x9c,
+  0xe4, 0x31, 0x61, 0xef, 0x9a, 0x12, 0x33, 0x69, 0xd4, 0xe0, 0x30, 0x6e,
+  0x1c, 0x67, 0x5d, 0xf9, 0x68, 0xd6, 0x31, 0x37, 0x6a, 0x49, 0xc5, 0x0b,
+  0x75, 0x99, 0x54, 0x65, 0x1e, 0x5f, 0x2d, 0x99, 0xcb, 0xa7, 0x41, 0xa4,
+  0xfa, 0xd2, 0xb5, 0xf0, 0xd4, 0x1e, 0x48, 0xec, 0x90, 0x3f, 0xd3, 0x7d,
+  0xb1, 0xff, 0x23, 0x96, 0x6b, 0x23, 0x35, 0xb0, 0xed, 0x9e, 0x5f, 0x3d,
+  0x31, 0x74, 0x48, 0x80, 0x7d, 0x90, 0x56, 0x6d, 0x10, 0xfe, 0x63, 0x7c,
+  0xee, 0x9a, 0xd3, 0xfd, 0x9f, 0x5f, 0x21, 0x09, 0x0d, 0x5e, 0xcc, 0xb3,
+  0x8d, 0x5a, 0x8f, 0xd8, 0xa0, 0x41, 0x35, 0xa3, 0x86, 0x73, 0x05, 0xae,
+  0xd9, 0x19, 0x7a, 0x3a, 0xcb, 0x20, 0xaf, 0x51, 0x91, 0xa3, 0xcc, 0x46,
+  0x4d, 0x47, 0x50, 0xc6, 0xfb, 0xdc, 0x15, 0x2c, 0x54, 0x71, 0xbf, 0xfe,
+  0x57, 0xfb, 0x89, 0xac, 0xff, 0xd0, 0xbb, 0x8f, 0x66, 0x3e, 0xef, 0xe4,
+  0x21, 0xaf, 0x80, 0x47, 0xff, 0x86, 0xdb, 0x39, 0x11, 0xc8, 0xe6, 0x50,
+  0xcd, 0x45, 0x6d, 0x59, 0x96, 0xca, 0x55, 0x76, 0x6d, 0xb5, 0x8e, 0xb0,
+  0xde, 0x09, 0x68, 0x00,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 269 (0x10d)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com
+        Validity
+            Not Before: Jun 29 17:06:20 2004 GMT
+            Not After : Jun 29 17:06:20 2024 GMT
+        Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:de:9d:d7:ea:57:18:49:a1:5b:eb:d7:5f:48:86:
+                    ea:be:dd:ff:e4:ef:67:1c:f4:65:68:b3:57:71:a0:
+                    5e:77:bb:ed:9b:49:e9:70:80:3d:56:18:63:08:6f:
+                    da:f2:cc:d0:3f:7f:02:54:22:54:10:d8:b2:81:d4:
+                    c0:75:3d:4b:7f:c7:77:c3:3e:78:ab:1a:03:b5:20:
+                    6b:2f:6a:2b:b1:c5:88:7e:c4:bb:1e:b0:c1:d8:45:
+                    27:6f:aa:37:58:f7:87:26:d7:d8:2d:f6:a9:17:b7:
+                    1f:72:36:4e:a6:17:3f:65:98:92:db:2a:6e:5d:a2:
+                    fe:88:e0:0b:de:7f:e5:8d:15:e1:eb:cb:3a:d5:e2:
+                    12:a2:13:2d:d8:8e:af:5f:12:3d:a0:08:05:08:b6:
+                    5c:a5:65:38:04:45:99:1e:a3:60:60:74:c5:41:a5:
+                    72:62:1b:62:c5:1f:6f:5f:1a:42:be:02:51:65:a8:
+                    ae:23:18:6a:fc:78:03:a9:4d:7f:80:c3:fa:ab:5a:
+                    fc:a1:40:a4:ca:19:16:fe:b2:c8:ef:5e:73:0d:ee:
+                    77:bd:9a:f6:79:98:bc:b1:07:67:a2:15:0d:dd:a0:
+                    58:c6:44:7b:0a:3e:62:28:5f:ba:41:07:53:58:cf:
+                    11:7e:38:74:c5:f8:ff:b5:69:90:8f:84:74:ea:97:
+                    1b:af
+                Exponent: 3 (0x3)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
+            X509v3 Authority Key Identifier: 
+                DirName:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com
+                serial:01
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.godaddy.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://certificates.godaddy.com/repository/root.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://certificates.godaddy.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+    Signature Algorithm: sha1WithRSAEncryption
+         b5:40:f9:a7:1d:f6:ea:fe:a4:1a:42:5a:44:f7:15:d4:85:46:
+         89:c0:be:9e:e3:e3:eb:c5:e3:58:89:8f:92:9f:57:a8:71:2c:
+         48:d1:81:b2:79:1f:ac:06:35:19:b0:4e:0e:58:1b:14:b3:98:
+         81:d1:04:1e:c8:07:c9:83:9f:78:44:0a:18:0b:98:dc:76:7a:
+         65:0d:0d:6d:80:c4:0b:01:1c:cb:ad:47:3e:71:be:77:4b:cc:
+         06:77:d0:f4:56:6b:1f:4b:13:9a:14:8a:88:23:a8:51:f0:83:
+         4c:ab:35:bf:46:7e:39:dc:75:a4:ae:e8:29:fb:ef:39:8f:4f:
+         55:67
+-----BEGIN CERTIFICATE-----
+MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
+bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
+Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
+QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
+BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
+DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
+YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
+ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
+N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
+r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
+f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
+U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
+TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
+VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
+SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
+biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
+MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
+AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
+ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
+Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
+IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
+bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
+QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
+WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
+SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert68[] = {
+  0x30, 0x82, 0x04, 0xfb, 0x30, 0x82, 0x04, 0x64, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x02, 0x01, 0x0d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xbb, 0x31,
+  0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61,
+  0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c,
+  0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+  0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63,
+  0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+  0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65,
+  0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34,
+  0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x30, 0x36, 0x32, 0x30, 0x5a, 0x17,
+  0x0d, 0x32, 0x34, 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x30, 0x36, 0x32,
+  0x30, 0x5a, 0x30, 0x63, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+  0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x18, 0x54, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44,
+  0x61, 0x64, 0x64, 0x79, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20,
+  0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x20, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, 0x00, 0x30, 0x82, 0x01, 0x08, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xde, 0x9d, 0xd7, 0xea, 0x57, 0x18, 0x49, 0xa1,
+  0x5b, 0xeb, 0xd7, 0x5f, 0x48, 0x86, 0xea, 0xbe, 0xdd, 0xff, 0xe4, 0xef,
+  0x67, 0x1c, 0xf4, 0x65, 0x68, 0xb3, 0x57, 0x71, 0xa0, 0x5e, 0x77, 0xbb,
+  0xed, 0x9b, 0x49, 0xe9, 0x70, 0x80, 0x3d, 0x56, 0x18, 0x63, 0x08, 0x6f,
+  0xda, 0xf2, 0xcc, 0xd0, 0x3f, 0x7f, 0x02, 0x54, 0x22, 0x54, 0x10, 0xd8,
+  0xb2, 0x81, 0xd4, 0xc0, 0x75, 0x3d, 0x4b, 0x7f, 0xc7, 0x77, 0xc3, 0x3e,
+  0x78, 0xab, 0x1a, 0x03, 0xb5, 0x20, 0x6b, 0x2f, 0x6a, 0x2b, 0xb1, 0xc5,
+  0x88, 0x7e, 0xc4, 0xbb, 0x1e, 0xb0, 0xc1, 0xd8, 0x45, 0x27, 0x6f, 0xaa,
+  0x37, 0x58, 0xf7, 0x87, 0x26, 0xd7, 0xd8, 0x2d, 0xf6, 0xa9, 0x17, 0xb7,
+  0x1f, 0x72, 0x36, 0x4e, 0xa6, 0x17, 0x3f, 0x65, 0x98, 0x92, 0xdb, 0x2a,
+  0x6e, 0x5d, 0xa2, 0xfe, 0x88, 0xe0, 0x0b, 0xde, 0x7f, 0xe5, 0x8d, 0x15,
+  0xe1, 0xeb, 0xcb, 0x3a, 0xd5, 0xe2, 0x12, 0xa2, 0x13, 0x2d, 0xd8, 0x8e,
+  0xaf, 0x5f, 0x12, 0x3d, 0xa0, 0x08, 0x05, 0x08, 0xb6, 0x5c, 0xa5, 0x65,
+  0x38, 0x04, 0x45, 0x99, 0x1e, 0xa3, 0x60, 0x60, 0x74, 0xc5, 0x41, 0xa5,
+  0x72, 0x62, 0x1b, 0x62, 0xc5, 0x1f, 0x6f, 0x5f, 0x1a, 0x42, 0xbe, 0x02,
+  0x51, 0x65, 0xa8, 0xae, 0x23, 0x18, 0x6a, 0xfc, 0x78, 0x03, 0xa9, 0x4d,
+  0x7f, 0x80, 0xc3, 0xfa, 0xab, 0x5a, 0xfc, 0xa1, 0x40, 0xa4, 0xca, 0x19,
+  0x16, 0xfe, 0xb2, 0xc8, 0xef, 0x5e, 0x73, 0x0d, 0xee, 0x77, 0xbd, 0x9a,
+  0xf6, 0x79, 0x98, 0xbc, 0xb1, 0x07, 0x67, 0xa2, 0x15, 0x0d, 0xdd, 0xa0,
+  0x58, 0xc6, 0x44, 0x7b, 0x0a, 0x3e, 0x62, 0x28, 0x5f, 0xba, 0x41, 0x07,
+  0x53, 0x58, 0xcf, 0x11, 0x7e, 0x38, 0x74, 0xc5, 0xf8, 0xff, 0xb5, 0x69,
+  0x90, 0x8f, 0x84, 0x74, 0xea, 0x97, 0x1b, 0xaf, 0x02, 0x01, 0x03, 0xa3,
+  0x82, 0x01, 0xe1, 0x30, 0x82, 0x01, 0xdd, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd2, 0xc4, 0xb0, 0xd2, 0x91, 0xd4,
+  0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd, 0xa8, 0x6a,
+  0xd4, 0xe3, 0x30, 0x81, 0xd2, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81,
+  0xca, 0x30, 0x81, 0xc7, 0xa1, 0x81, 0xc1, 0xa4, 0x81, 0xbe, 0x30, 0x81,
+  0xbb, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b,
+  0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c,
+  0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20,
+  0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69,
+  0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31,
+  0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c,
+  0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20,
+  0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09,
+  0x01, 0x16, 0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69,
+  0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x82, 0x01, 0x01, 0x30,
+  0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+  0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f,
+  0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x44, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+  0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e,
+  0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x72,
+  0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4b, 0x06, 0x03, 0x55,
+  0x1d, 0x20, 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0x06, 0x04, 0x55, 0x1d,
+  0x20, 0x00, 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+  0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
+  0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+  0x03, 0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xb5,
+  0x40, 0xf9, 0xa7, 0x1d, 0xf6, 0xea, 0xfe, 0xa4, 0x1a, 0x42, 0x5a, 0x44,
+  0xf7, 0x15, 0xd4, 0x85, 0x46, 0x89, 0xc0, 0xbe, 0x9e, 0xe3, 0xe3, 0xeb,
+  0xc5, 0xe3, 0x58, 0x89, 0x8f, 0x92, 0x9f, 0x57, 0xa8, 0x71, 0x2c, 0x48,
+  0xd1, 0x81, 0xb2, 0x79, 0x1f, 0xac, 0x06, 0x35, 0x19, 0xb0, 0x4e, 0x0e,
+  0x58, 0x1b, 0x14, 0xb3, 0x98, 0x81, 0xd1, 0x04, 0x1e, 0xc8, 0x07, 0xc9,
+  0x83, 0x9f, 0x78, 0x44, 0x0a, 0x18, 0x0b, 0x98, 0xdc, 0x76, 0x7a, 0x65,
+  0x0d, 0x0d, 0x6d, 0x80, 0xc4, 0x0b, 0x01, 0x1c, 0xcb, 0xad, 0x47, 0x3e,
+  0x71, 0xbe, 0x77, 0x4b, 0xcc, 0x06, 0x77, 0xd0, 0xf4, 0x56, 0x6b, 0x1f,
+  0x4b, 0x13, 0x9a, 0x14, 0x8a, 0x88, 0x23, 0xa8, 0x51, 0xf0, 0x83, 0x4c,
+  0xab, 0x35, 0xbf, 0x46, 0x7e, 0x39, 0xdc, 0x75, 0xa4, 0xae, 0xe8, 0x29,
+  0xfb, 0xef, 0x39, 0x8f, 0x4f, 0x55, 0x67,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            16:90:c3:29:b6:78:06:07:51:1f:05:b0:34:48:46:cb
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Apr 16 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO High-Assurance Secure Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e7:87:da:c0:77:e4:bb:3a:fa:6a:24:c8:80:41:
+                    ac:d2:16:13:15:3d:fa:f7:f8:2a:76:dc:a8:2d:39:
+                    08:ce:48:4a:be:0f:7d:f0:de:ba:bb:47:d5:bd:2d:
+                    d7:1b:ab:0f:20:81:23:08:72:b1:c0:11:95:0d:e6:
+                    ea:a9:87:ff:c7:6e:1e:4f:66:32:ba:53:bc:05:aa:
+                    1c:2c:0c:ef:4d:37:47:6b:10:0c:db:c5:a0:98:7e:
+                    58:db:37:d6:ae:e9:06:bd:d7:a8:65:f3:37:b9:c7:
+                    6d:ce:77:c7:26:e0:d7:74:1f:a6:98:16:bb:0c:6b:
+                    c8:be:77:d0:ef:58:a7:29:a0:b9:b8:69:05:36:cb:
+                    b2:da:58:a3:0b:75:ad:3d:8b:22:82:20:3e:70:86:
+                    99:1c:b9:4f:cf:77:a4:07:1a:23:63:d1:38:56:84:
+                    ec:bf:8f:c5:4e:f4:18:96:9b:1a:e8:93:ec:8d:af:
+                    15:9c:24:f0:5a:3b:e8:0f:b9:a8:5a:01:d3:b2:1c:
+                    60:c9:9c:52:04:dd:92:a7:fe:0c:ac:e2:45:8d:03:
+                    61:bc:79:e0:77:2e:87:41:3c:58:5f:cb:f5:c5:77:
+                    f2:58:c8:4d:28:d0:9a:fa:f3:73:09:24:68:74:bc:
+                    20:4c:d8:2c:b0:aa:e8:d9:4e:6d:f2:8c:24:d3:93:
+                    5d:91
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                3F:D5:B5:D0:D6:44:79:50:4A:17:A3:9B:8C:4A:DC:B8:B0:22:64:6B
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         13:85:1f:52:80:18:c9:53:f7:fe:2e:1a:af:cc:d9:0b:3c:c2:
+         d3:85:81:10:f0:28:8d:b9:40:7e:2c:9e:8f:d6:36:86:0a:4c:
+         14:2d:d6:97:43:92:41:19:37:4b:96:9e:eb:a9:30:79:12:95:
+         b3:02:36:57:ed:2b:b9:1d:98:1a:a3:18:0a:3f:9b:39:8b:cd:
+         a1:49:29:4c:2f:f9:d0:95:8c:c8:4d:95:ba:a8:43:cf:33:aa:
+         25:2a:5a:0e:aa:27:c9:4e:6b:b1:e6:73:1f:b3:74:04:c3:f3:
+         4c:e2:a8:eb:67:b7:5d:b8:08:05:1a:56:9a:54:29:85:f5:29:
+         4e:80:3b:95:d0:7b:53:96:11:56:c1:02:d3:ea:b2:7f:ca:8f:
+         9c:70:4a:14:8d:5a:b9:16:60:75:d6:cd:27:1e:16:cd:5b:33:
+         8e:79:40:cf:28:48:e7:dc:71:16:4e:74:91:75:b9:2a:8c:f1:
+         70:ac:26:dd:04:b9:40:c2:85:de:1c:93:40:d0:cc:6e:c3:9b:
+         aa:ef:60:65:df:60:22:f0:5a:a5:7a:a2:2f:e4:70:73:ee:3c:
+         d4:26:2b:68:07:c1:20:7a:e8:98:5a:3e:7b:9f:02:8b:62:c0:
+         85:81:80:60:35:7e:a5:1d:0c:d2:9c:df:62:45:0d:db:fc:37:
+         fb:f5:25:22
+-----BEGIN CERTIFICATE-----
+MIIE/DCCA+SgAwIBAgIQFpDDKbZ4BgdRHwWwNEhGyzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDQxNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYkxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMS8wLQYD
+VQQDEyZDT01PRE8gSGlnaC1Bc3N1cmFuY2UgU2VjdXJlIFNlcnZlciBDQTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOeH2sB35Ls6+mokyIBBrNIWExU9
++vf4KnbcqC05CM5ISr4PffDeurtH1b0t1xurDyCBIwhyscARlQ3m6qmH/8duHk9m
+MrpTvAWqHCwM7003R2sQDNvFoJh+WNs31q7pBr3XqGXzN7nHbc53xybg13QfppgW
+uwxryL530O9YpymgubhpBTbLstpYowt1rT2LIoIgPnCGmRy5T893pAcaI2PROFaE
+7L+PxU70GJabGuiT7I2vFZwk8Fo76A+5qFoB07IcYMmcUgTdkqf+DKziRY0DYbx5
+4Hcuh0E8WF/L9cV38ljITSjQmvrzcwkkaHS8IEzYLLCq6NlObfKMJNOTXZECAwEA
+AaOCAXcwggFzMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1Ud
+DgQWBBQ/1bXQ1kR5UEoXo5uMSty4sCJkazAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDeg
+NYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJv
+b3QuY3JsMIGzBggrBgEFBQcBAQSBpjCBozA/BggrBgEFBQcwAoYzaHR0cDovL2Ny
+dC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsG
+AQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0ND
+QS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJ
+KoZIhvcNAQEFBQADggEBABOFH1KAGMlT9/4uGq/M2Qs8wtOFgRDwKI25QH4sno/W
+NoYKTBQt1pdDkkEZN0uWnuupMHkSlbMCNlftK7kdmBqjGAo/mzmLzaFJKUwv+dCV
+jMhNlbqoQ88zqiUqWg6qJ8lOa7Hmcx+zdATD80ziqOtnt124CAUaVppUKYX1KU6A
+O5XQe1OWEVbBAtPqsn/Kj5xwShSNWrkWYHXWzSceFs1bM455QM8oSOfccRZOdJF1
+uSqM8XCsJt0EuUDChd4ck0DQzG7Dm6rvYGXfYCLwWqV6oi/kcHPuPNQmK2gHwSB6
+6JhaPnufAotiwIWBgGA1fqUdDNKc32JFDdv8N/v1JSI=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert69[] = {
+  0x30, 0x82, 0x04, 0xfc, 0x30, 0x82, 0x03, 0xe4, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x16, 0x90, 0xc3, 0x29, 0xb6, 0x78, 0x06, 0x07, 0x51,
+  0x1f, 0x05, 0xb0, 0x34, 0x48, 0x46, 0xcb, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x34, 0x31,
+  0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x89, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+  0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+  0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+  0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+  0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x26, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+  0x48, 0x69, 0x67, 0x68, 0x2d, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e,
+  0x63, 0x65, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xe7, 0x87, 0xda, 0xc0, 0x77, 0xe4, 0xbb, 0x3a,
+  0xfa, 0x6a, 0x24, 0xc8, 0x80, 0x41, 0xac, 0xd2, 0x16, 0x13, 0x15, 0x3d,
+  0xfa, 0xf7, 0xf8, 0x2a, 0x76, 0xdc, 0xa8, 0x2d, 0x39, 0x08, 0xce, 0x48,
+  0x4a, 0xbe, 0x0f, 0x7d, 0xf0, 0xde, 0xba, 0xbb, 0x47, 0xd5, 0xbd, 0x2d,
+  0xd7, 0x1b, 0xab, 0x0f, 0x20, 0x81, 0x23, 0x08, 0x72, 0xb1, 0xc0, 0x11,
+  0x95, 0x0d, 0xe6, 0xea, 0xa9, 0x87, 0xff, 0xc7, 0x6e, 0x1e, 0x4f, 0x66,
+  0x32, 0xba, 0x53, 0xbc, 0x05, 0xaa, 0x1c, 0x2c, 0x0c, 0xef, 0x4d, 0x37,
+  0x47, 0x6b, 0x10, 0x0c, 0xdb, 0xc5, 0xa0, 0x98, 0x7e, 0x58, 0xdb, 0x37,
+  0xd6, 0xae, 0xe9, 0x06, 0xbd, 0xd7, 0xa8, 0x65, 0xf3, 0x37, 0xb9, 0xc7,
+  0x6d, 0xce, 0x77, 0xc7, 0x26, 0xe0, 0xd7, 0x74, 0x1f, 0xa6, 0x98, 0x16,
+  0xbb, 0x0c, 0x6b, 0xc8, 0xbe, 0x77, 0xd0, 0xef, 0x58, 0xa7, 0x29, 0xa0,
+  0xb9, 0xb8, 0x69, 0x05, 0x36, 0xcb, 0xb2, 0xda, 0x58, 0xa3, 0x0b, 0x75,
+  0xad, 0x3d, 0x8b, 0x22, 0x82, 0x20, 0x3e, 0x70, 0x86, 0x99, 0x1c, 0xb9,
+  0x4f, 0xcf, 0x77, 0xa4, 0x07, 0x1a, 0x23, 0x63, 0xd1, 0x38, 0x56, 0x84,
+  0xec, 0xbf, 0x8f, 0xc5, 0x4e, 0xf4, 0x18, 0x96, 0x9b, 0x1a, 0xe8, 0x93,
+  0xec, 0x8d, 0xaf, 0x15, 0x9c, 0x24, 0xf0, 0x5a, 0x3b, 0xe8, 0x0f, 0xb9,
+  0xa8, 0x5a, 0x01, 0xd3, 0xb2, 0x1c, 0x60, 0xc9, 0x9c, 0x52, 0x04, 0xdd,
+  0x92, 0xa7, 0xfe, 0x0c, 0xac, 0xe2, 0x45, 0x8d, 0x03, 0x61, 0xbc, 0x79,
+  0xe0, 0x77, 0x2e, 0x87, 0x41, 0x3c, 0x58, 0x5f, 0xcb, 0xf5, 0xc5, 0x77,
+  0xf2, 0x58, 0xc8, 0x4d, 0x28, 0xd0, 0x9a, 0xfa, 0xf3, 0x73, 0x09, 0x24,
+  0x68, 0x74, 0xbc, 0x20, 0x4c, 0xd8, 0x2c, 0xb0, 0xaa, 0xe8, 0xd9, 0x4e,
+  0x6d, 0xf2, 0x8c, 0x24, 0xd3, 0x93, 0x5d, 0x91, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+  0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+  0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x3f, 0xd5, 0xb5, 0xd0, 0xd6, 0x44, 0x79,
+  0x50, 0x4a, 0x17, 0xa3, 0x9b, 0x8c, 0x4a, 0xdc, 0xb8, 0xb0, 0x22, 0x64,
+  0x6b, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+  0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+  0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+  0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+  0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+  0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x13, 0x85, 0x1f, 0x52, 0x80, 0x18, 0xc9, 0x53,
+  0xf7, 0xfe, 0x2e, 0x1a, 0xaf, 0xcc, 0xd9, 0x0b, 0x3c, 0xc2, 0xd3, 0x85,
+  0x81, 0x10, 0xf0, 0x28, 0x8d, 0xb9, 0x40, 0x7e, 0x2c, 0x9e, 0x8f, 0xd6,
+  0x36, 0x86, 0x0a, 0x4c, 0x14, 0x2d, 0xd6, 0x97, 0x43, 0x92, 0x41, 0x19,
+  0x37, 0x4b, 0x96, 0x9e, 0xeb, 0xa9, 0x30, 0x79, 0x12, 0x95, 0xb3, 0x02,
+  0x36, 0x57, 0xed, 0x2b, 0xb9, 0x1d, 0x98, 0x1a, 0xa3, 0x18, 0x0a, 0x3f,
+  0x9b, 0x39, 0x8b, 0xcd, 0xa1, 0x49, 0x29, 0x4c, 0x2f, 0xf9, 0xd0, 0x95,
+  0x8c, 0xc8, 0x4d, 0x95, 0xba, 0xa8, 0x43, 0xcf, 0x33, 0xaa, 0x25, 0x2a,
+  0x5a, 0x0e, 0xaa, 0x27, 0xc9, 0x4e, 0x6b, 0xb1, 0xe6, 0x73, 0x1f, 0xb3,
+  0x74, 0x04, 0xc3, 0xf3, 0x4c, 0xe2, 0xa8, 0xeb, 0x67, 0xb7, 0x5d, 0xb8,
+  0x08, 0x05, 0x1a, 0x56, 0x9a, 0x54, 0x29, 0x85, 0xf5, 0x29, 0x4e, 0x80,
+  0x3b, 0x95, 0xd0, 0x7b, 0x53, 0x96, 0x11, 0x56, 0xc1, 0x02, 0xd3, 0xea,
+  0xb2, 0x7f, 0xca, 0x8f, 0x9c, 0x70, 0x4a, 0x14, 0x8d, 0x5a, 0xb9, 0x16,
+  0x60, 0x75, 0xd6, 0xcd, 0x27, 0x1e, 0x16, 0xcd, 0x5b, 0x33, 0x8e, 0x79,
+  0x40, 0xcf, 0x28, 0x48, 0xe7, 0xdc, 0x71, 0x16, 0x4e, 0x74, 0x91, 0x75,
+  0xb9, 0x2a, 0x8c, 0xf1, 0x70, 0xac, 0x26, 0xdd, 0x04, 0xb9, 0x40, 0xc2,
+  0x85, 0xde, 0x1c, 0x93, 0x40, 0xd0, 0xcc, 0x6e, 0xc3, 0x9b, 0xaa, 0xef,
+  0x60, 0x65, 0xdf, 0x60, 0x22, 0xf0, 0x5a, 0xa5, 0x7a, 0xa2, 0x2f, 0xe4,
+  0x70, 0x73, 0xee, 0x3c, 0xd4, 0x26, 0x2b, 0x68, 0x07, 0xc1, 0x20, 0x7a,
+  0xe8, 0x98, 0x5a, 0x3e, 0x7b, 0x9f, 0x02, 0x8b, 0x62, 0xc0, 0x85, 0x81,
+  0x80, 0x60, 0x35, 0x7e, 0xa5, 0x1d, 0x0c, 0xd2, 0x9c, 0xdf, 0x62, 0x45,
+  0x0d, 0xdb, 0xfc, 0x37, 0xfb, 0xf5, 0x25, 0x22,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            45:4f:a2:0d:78:11:74:59:f8:c6:ab:3c:7b:cd:03:0e
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: Jul 29 00:00:00 2011 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=BR, O=TrustSign Certificadora Digital, OU=Security Dept., CN=TrustSign BR Certification Authority (OV)
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a3:fb:cb:bf:d7:c0:51:de:8e:5f:6e:76:a7:1f:
+                    c3:30:67:68:ef:63:a9:0d:3c:7a:0e:f6:dd:26:ff:
+                    bc:7a:26:d5:b6:22:91:08:b8:38:43:e1:15:3d:0b:
+                    f0:b0:df:9d:34:40:06:5e:ea:3b:e9:9b:2e:23:d3:
+                    eb:1f:17:1c:16:ad:5a:78:41:e6:2d:61:ed:7d:2c:
+                    5a:5d:8a:6c:0e:70:6b:ff:ce:c4:80:50:14:cc:2b:
+                    60:0f:19:3d:f1:6d:e0:a7:a3:59:22:97:73:ec:4a:
+                    d6:ba:f0:9e:a9:0b:f4:66:3e:13:5f:c8:72:5f:04:
+                    4a:13:21:e6:80:85:1f:63:5a:26:f0:c4:25:9b:07:
+                    33:71:ea:32:26:04:b2:d8:68:ff:6a:e6:09:5f:09:
+                    83:0c:c3:a8:e5:5a:bf:6c:86:b2:28:6a:94:a3:74:
+                    99:4a:d8:d0:11:e5:e4:ee:8c:b2:2c:4d:72:86:1b:
+                    17:4d:ee:cc:9b:5a:b2:36:7d:05:92:17:47:9b:f7:
+                    06:9d:96:be:ac:da:c4:db:97:c0:d4:10:cb:11:26:
+                    62:59:17:05:14:63:cb:81:4f:5a:a3:9d:cf:50:e7:
+                    4a:b6:e3:30:d5:29:44:ac:99:45:8c:0d:d0:97:66:
+                    bb:38:f6:ad:0f:4d:ea:bb:0a:8b:02:27:11:37:eb:
+                    42:bb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                22:82:72:0D:B6:41:E3:DC:E4:81:8B:FB:86:02:11:71:93:FA:9B:4D
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.2.38
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         3e:d9:cd:2e:64:0f:59:01:5c:22:22:b1:71:44:9d:59:4d:4c:
+         8c:8f:10:59:5e:f6:ba:f8:5a:e3:4c:62:ea:09:8d:d7:9b:69:
+         a1:af:90:1d:de:f9:18:d6:83:c2:a5:e9:a8:ac:b6:ba:bf:db:
+         4d:0a:50:f1:78:a3:13:91:6b:26:d8:e9:94:bb:2c:fb:0d:6f:
+         7e:a7:f5:16:e2:99:77:d3:c6:52:3f:06:6e:18:f0:2e:28:c7:
+         72:01:d7:9c:0b:12:40:76:08:7a:5c:69:eb:a1:d9:82:97:26:
+         c2:76:40:47:0f:56:54:2d:2c:92:65:98:28:b7:10:b2:dc:9c:
+         26:b5:54:11:d6:be:b8:b6:03:9c:f0:b5:a5:f7:6e:f1:97:9c:
+         bd:88:23:a5:48:c7:8c:ca:0d:26:fd:48:09:a7:22:92:04:2a:
+         3a:7e:97:a4:86:ef:9f:ff:2c:4b:e1:00:14:c1:55:6d:c1:5c:
+         91:57:ec:8d:43:c5:f9:7d:b4:0c:06:34:c3:b4:a7:67:87:b6:
+         c4:cf:1a:35:08:95:11:20:21:a1:77:bf:2f:19:8a:73:81:c2:
+         87:23:eb:d3:99:b2:7f:27:89:d2:8e:40:fa:3d:ee:06:49:0b:
+         fe:f4:a4:34:49:77:88:3d:42:91:96:b4:2c:9c:0b:e6:d4:41:
+         c4:df:ad:90
+-----BEGIN CERTIFICATE-----
+MIIE/jCCA+agAwIBAgIQRU+iDXgRdFn4xqs8e80DDjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTExMDcyOTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYQxCzAJBgNVBAYTAkJSMSgwJgYDVQQKEx9UcnVzdFNpZ24gQ2VydGlmaWNhZG9y
+YSBEaWdpdGFsMRcwFQYDVQQLEw5TZWN1cml0eSBEZXB0LjEyMDAGA1UEAxMpVHJ1
+c3RTaWduIEJSIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IChPVikwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCj+8u/18BR3o5fbnanH8MwZ2jvY6kNPHoO
+9t0m/7x6JtW2IpEIuDhD4RU9C/Cw3500QAZe6jvpmy4j0+sfFxwWrVp4QeYtYe19
+LFpdimwOcGv/zsSAUBTMK2APGT3xbeCno1kil3PsSta68J6pC/RmPhNfyHJfBEoT
+IeaAhR9jWibwxCWbBzNx6jImBLLYaP9q5glfCYMMw6jlWr9shrIoapSjdJlK2NAR
+5eTujLIsTXKGGxdN7sybWrI2fQWSF0eb9wadlr6s2sTbl8DUEMsRJmJZFwUUY8uB
+T1qjnc9Q50q24zDVKUSsmUWMDdCXZrs49q0PTeq7CosCJxE360K7AgMBAAGjggF+
+MIIBejAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQU
+IoJyDbZB49zkgYv7hgIRcZP6m00wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwGAYDVR0gBBEwDzANBgsrBgEEAbIxAQICJjBEBgNVHR8EPTA7MDmg
+N6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENB
+Um9vdC5jcmwwgbMGCCsGAQUFBwEBBIGmMIGjMD8GCCsGAQUFBzAChjNodHRwOi8v
+Y3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5wN2MwOQYI
+KwYBBQUHMAKGLWh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdFVUTlNH
+Q0NBLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTAN
+BgkqhkiG9w0BAQUFAAOCAQEAPtnNLmQPWQFcIiKxcUSdWU1MjI8QWV72uvha40xi
+6gmN15tpoa+QHd75GNaDwqXpqKy2ur/bTQpQ8XijE5FrJtjplLss+w1vfqf1FuKZ
+d9PGUj8GbhjwLijHcgHXnAsSQHYIelxp66HZgpcmwnZARw9WVC0skmWYKLcQstyc
+JrVUEda+uLYDnPC1pfdu8ZecvYgjpUjHjMoNJv1ICacikgQqOn6XpIbvn/8sS+EA
+FMFVbcFckVfsjUPF+X20DAY0w7SnZ4e2xM8aNQiVESAhoXe/LxmKc4HChyPr05my
+fyeJ0o5A+j3uBkkL/vSkNEl3iD1CkZa0LJwL5tRBxN+tkA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert70[] = {
+  0x30, 0x82, 0x04, 0xfe, 0x30, 0x82, 0x03, 0xe6, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x45, 0x4f, 0xa2, 0x0d, 0x78, 0x11, 0x74, 0x59, 0xf8,
+  0xc6, 0xab, 0x3c, 0x7b, 0xcd, 0x03, 0x0e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x37, 0x32,
+  0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x81, 0x84, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x42, 0x52, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x1f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x64, 0x6f, 0x72,
+  0x61, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x31, 0x17, 0x30,
+  0x15, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0e, 0x53, 0x65, 0x63, 0x75,
+  0x72, 0x69, 0x74, 0x79, 0x20, 0x44, 0x65, 0x70, 0x74, 0x2e, 0x31, 0x32,
+  0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x42, 0x52, 0x20, 0x43, 0x65,
+  0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x4f,
+  0x56, 0x29, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3,
+  0xfb, 0xcb, 0xbf, 0xd7, 0xc0, 0x51, 0xde, 0x8e, 0x5f, 0x6e, 0x76, 0xa7,
+  0x1f, 0xc3, 0x30, 0x67, 0x68, 0xef, 0x63, 0xa9, 0x0d, 0x3c, 0x7a, 0x0e,
+  0xf6, 0xdd, 0x26, 0xff, 0xbc, 0x7a, 0x26, 0xd5, 0xb6, 0x22, 0x91, 0x08,
+  0xb8, 0x38, 0x43, 0xe1, 0x15, 0x3d, 0x0b, 0xf0, 0xb0, 0xdf, 0x9d, 0x34,
+  0x40, 0x06, 0x5e, 0xea, 0x3b, 0xe9, 0x9b, 0x2e, 0x23, 0xd3, 0xeb, 0x1f,
+  0x17, 0x1c, 0x16, 0xad, 0x5a, 0x78, 0x41, 0xe6, 0x2d, 0x61, 0xed, 0x7d,
+  0x2c, 0x5a, 0x5d, 0x8a, 0x6c, 0x0e, 0x70, 0x6b, 0xff, 0xce, 0xc4, 0x80,
+  0x50, 0x14, 0xcc, 0x2b, 0x60, 0x0f, 0x19, 0x3d, 0xf1, 0x6d, 0xe0, 0xa7,
+  0xa3, 0x59, 0x22, 0x97, 0x73, 0xec, 0x4a, 0xd6, 0xba, 0xf0, 0x9e, 0xa9,
+  0x0b, 0xf4, 0x66, 0x3e, 0x13, 0x5f, 0xc8, 0x72, 0x5f, 0x04, 0x4a, 0x13,
+  0x21, 0xe6, 0x80, 0x85, 0x1f, 0x63, 0x5a, 0x26, 0xf0, 0xc4, 0x25, 0x9b,
+  0x07, 0x33, 0x71, 0xea, 0x32, 0x26, 0x04, 0xb2, 0xd8, 0x68, 0xff, 0x6a,
+  0xe6, 0x09, 0x5f, 0x09, 0x83, 0x0c, 0xc3, 0xa8, 0xe5, 0x5a, 0xbf, 0x6c,
+  0x86, 0xb2, 0x28, 0x6a, 0x94, 0xa3, 0x74, 0x99, 0x4a, 0xd8, 0xd0, 0x11,
+  0xe5, 0xe4, 0xee, 0x8c, 0xb2, 0x2c, 0x4d, 0x72, 0x86, 0x1b, 0x17, 0x4d,
+  0xee, 0xcc, 0x9b, 0x5a, 0xb2, 0x36, 0x7d, 0x05, 0x92, 0x17, 0x47, 0x9b,
+  0xf7, 0x06, 0x9d, 0x96, 0xbe, 0xac, 0xda, 0xc4, 0xdb, 0x97, 0xc0, 0xd4,
+  0x10, 0xcb, 0x11, 0x26, 0x62, 0x59, 0x17, 0x05, 0x14, 0x63, 0xcb, 0x81,
+  0x4f, 0x5a, 0xa3, 0x9d, 0xcf, 0x50, 0xe7, 0x4a, 0xb6, 0xe3, 0x30, 0xd5,
+  0x29, 0x44, 0xac, 0x99, 0x45, 0x8c, 0x0d, 0xd0, 0x97, 0x66, 0xbb, 0x38,
+  0xf6, 0xad, 0x0f, 0x4d, 0xea, 0xbb, 0x0a, 0x8b, 0x02, 0x27, 0x11, 0x37,
+  0xeb, 0x42, 0xbb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7e,
+  0x30, 0x82, 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26,
+  0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54,
+  0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x22, 0x82, 0x72, 0x0d, 0xb6, 0x41, 0xe3, 0xdc, 0xe4, 0x81, 0x8b, 0xfb,
+  0x86, 0x02, 0x11, 0x71, 0x93, 0xfa, 0x9b, 0x4d, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b,
+  0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x26, 0x30, 0x44,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+  0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+  0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6,
+  0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+  0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+  0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47,
+  0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3e, 0xd9, 0xcd, 0x2e, 0x64, 0x0f,
+  0x59, 0x01, 0x5c, 0x22, 0x22, 0xb1, 0x71, 0x44, 0x9d, 0x59, 0x4d, 0x4c,
+  0x8c, 0x8f, 0x10, 0x59, 0x5e, 0xf6, 0xba, 0xf8, 0x5a, 0xe3, 0x4c, 0x62,
+  0xea, 0x09, 0x8d, 0xd7, 0x9b, 0x69, 0xa1, 0xaf, 0x90, 0x1d, 0xde, 0xf9,
+  0x18, 0xd6, 0x83, 0xc2, 0xa5, 0xe9, 0xa8, 0xac, 0xb6, 0xba, 0xbf, 0xdb,
+  0x4d, 0x0a, 0x50, 0xf1, 0x78, 0xa3, 0x13, 0x91, 0x6b, 0x26, 0xd8, 0xe9,
+  0x94, 0xbb, 0x2c, 0xfb, 0x0d, 0x6f, 0x7e, 0xa7, 0xf5, 0x16, 0xe2, 0x99,
+  0x77, 0xd3, 0xc6, 0x52, 0x3f, 0x06, 0x6e, 0x18, 0xf0, 0x2e, 0x28, 0xc7,
+  0x72, 0x01, 0xd7, 0x9c, 0x0b, 0x12, 0x40, 0x76, 0x08, 0x7a, 0x5c, 0x69,
+  0xeb, 0xa1, 0xd9, 0x82, 0x97, 0x26, 0xc2, 0x76, 0x40, 0x47, 0x0f, 0x56,
+  0x54, 0x2d, 0x2c, 0x92, 0x65, 0x98, 0x28, 0xb7, 0x10, 0xb2, 0xdc, 0x9c,
+  0x26, 0xb5, 0x54, 0x11, 0xd6, 0xbe, 0xb8, 0xb6, 0x03, 0x9c, 0xf0, 0xb5,
+  0xa5, 0xf7, 0x6e, 0xf1, 0x97, 0x9c, 0xbd, 0x88, 0x23, 0xa5, 0x48, 0xc7,
+  0x8c, 0xca, 0x0d, 0x26, 0xfd, 0x48, 0x09, 0xa7, 0x22, 0x92, 0x04, 0x2a,
+  0x3a, 0x7e, 0x97, 0xa4, 0x86, 0xef, 0x9f, 0xff, 0x2c, 0x4b, 0xe1, 0x00,
+  0x14, 0xc1, 0x55, 0x6d, 0xc1, 0x5c, 0x91, 0x57, 0xec, 0x8d, 0x43, 0xc5,
+  0xf9, 0x7d, 0xb4, 0x0c, 0x06, 0x34, 0xc3, 0xb4, 0xa7, 0x67, 0x87, 0xb6,
+  0xc4, 0xcf, 0x1a, 0x35, 0x08, 0x95, 0x11, 0x20, 0x21, 0xa1, 0x77, 0xbf,
+  0x2f, 0x19, 0x8a, 0x73, 0x81, 0xc2, 0x87, 0x23, 0xeb, 0xd3, 0x99, 0xb2,
+  0x7f, 0x27, 0x89, 0xd2, 0x8e, 0x40, 0xfa, 0x3d, 0xee, 0x06, 0x49, 0x0b,
+  0xfe, 0xf4, 0xa4, 0x34, 0x49, 0x77, 0x88, 0x3d, 0x42, 0x91, 0x96, 0xb4,
+  0x2c, 0x9c, 0x0b, 0xe6, 0xd4, 0x41, 0xc4, 0xdf, 0xad, 0x90,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120025006 (0x7276fae)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+        Validity
+            Not Before: Apr 25 17:41:36 2012 GMT
+            Not After : Apr 25 17:40:55 2020 GMT
+        Subject: CN=Microsoft Internet Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (4096 bit)
+                Modulus:
+                    00:be:40:54:11:aa:28:79:66:5e:a9:bb:b0:93:62:
+                    4b:73:19:aa:e9:2f:91:4c:6d:97:37:25:68:b8:b0:
+                    36:50:94:5c:ef:ff:c4:c4:17:76:43:57:fc:a0:e1:
+                    c6:33:ec:30:62:be:35:0f:23:7a:96:4d:6c:74:62:
+                    09:7e:31:48:4e:9f:4d:aa:5b:b3:16:b7:fe:a0:29:
+                    2f:65:a5:4b:ac:6a:56:8a:bf:28:70:6a:df:37:18:
+                    df:ec:c6:87:22:3e:16:dd:38:1c:0f:e2:05:be:77:
+                    ab:ee:85:8a:1e:0d:23:3f:e9:24:e1:90:47:b2:67:
+                    3b:15:3e:55:20:3c:f9:f5:42:d7:af:c5:66:0d:9a:
+                    94:17:b9:6a:67:7c:a8:fa:b1:26:00:04:23:d5:4d:
+                    a3:ae:8d:79:60:b1:8e:83:4f:cd:bb:77:78:27:b2:
+                    e8:74:ad:1f:4c:34:80:31:7f:8b:e4:50:7e:6f:7f:
+                    cf:55:da:c6:fb:b6:5a:f5:5d:1d:38:94:a5:fe:b0:
+                    8d:22:63:23:e7:70:40:40:bf:89:aa:54:46:25:03:
+                    ec:a4:f2:ad:c0:40:b3:72:fe:94:b5:d9:96:b2:1b:
+                    73:5e:d5:f4:6b:47:41:79:a4:da:f3:8e:2e:8d:38:
+                    4d:c9:5e:17:1c:ae:b5:4c:36:6b:e8:7f:d9:24:c7:
+                    28:f0:a7:86:be:e8:1d:08:b4:db:72:23:64:d1:42:
+                    f3:f5:4c:da:ac:f3:b6:a5:75:68:fc:f0:bb:02:61:
+                    5d:1e:7a:07:52:29:be:74:12:42:53:9c:8d:cc:e9:
+                    88:7b:a4:55:36:08:58:8c:21:89:c5:ba:13:e8:6d:
+                    9b:81:8a:77:c6:38:6c:f5:3a:1d:91:37:57:2b:a9:
+                    eb:2b:46:4a:a8:97:28:8b:a3:7e:4a:dd:8d:a7:d3:
+                    02:10:7c:96:b0:c8:bc:86:28:06:d6:57:a2:a7:66:
+                    f1:17:d6:cc:5f:55:12:3d:59:03:a2:c7:d2:d6:69:
+                    b4:0d:25:f9:f1:d3:94:56:16:2e:26:bc:96:f9:ba:
+                    1c:51:9b:81:f1:37:bc:77:ee:7e:d9:9a:78:f6:41:
+                    b4:71:df:10:25:5b:b2:e1:3a:c7:7b:f2:6c:b3:19:
+                    b7:20:e7:89:e9:c4:a5:6a:a4:7c:39:24:ee:e7:5d:
+                    2b:2b:c9:91:fe:a1:3c:33:25:bd:c5:41:14:92:ee:
+                    cf:56:3a:26:13:75:ca:11:5c:c6:27:ab:49:88:ab:
+                    55:72:fc:65:48:dc:ae:cd:f1:8f:8b:10:66:2a:90:
+                    2e:0b:8b:b7:fe:38:75:cd:b1:75:80:b6:8f:cf:43:
+                    1c:21:29:93:0f:e3:13:b9:e2:b0:c8:b2:46:2a:5a:
+                    14:0c:1d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+            X509v3 Subject Key Identifier: 
+                2A:4D:97:95:5D:34:7E:9D:B6:E6:33:BE:9C:27:C1:70:7E:67:DB:C1
+    Signature Algorithm: sha1WithRSAEncryption
+         23:3e:87:6e:d3:ca:d6:0e:b0:36:25:8d:cc:0a:ce:dc:5d:74:
+         f3:42:1b:ef:aa:c0:49:73:85:cc:7d:2f:77:09:23:c5:2c:50:
+         1d:46:c3:68:68:ef:10:32:d9:4d:7e:e3:5a:e3:1d:18:3c:d2:
+         40:54:cd:ef:59:ee:df:fe:fb:70:4a:bc:d8:74:37:2b:dc:a4:
+         68:71:50:ba:63:cb:44:dd:11:48:ff:f8:f1:ff:60:ba:7c:aa:
+         30:7d:dd:17:b1:77:ea:50:90:20:00:0b:a3:3d:5d:98:71:51:
+         9f:dd:2d:c0:78:5e:0d:55:b1:83:45:de:e5:59:98:6d:a4:e1:
+         69:7c:32:d0:04:7b:f7:a9:1d:97:3b:d5:59:bf:cb:6f:9d:a4:
+         b6:a5:bb:41:11:ed:c8:91:83:15:55:ae:59:36:b7:9f:6a:f0:
+         b8:38:f9:7c:32:25:95:cc:33:f1:31:e7:df:cb:78:4b:36:1f:
+         f4:55:e0:bd:28:f9:ca:b9:64:99:ce:eb:61:e9:81:72:94:d3:
+         9b:cd:0a:2b:2a:a4:94:83:ae:6a:0c:23:44:0e:35:ad:a1:e9:
+         ec:d8:d7:75:90:8d:e1:d6:b3:c5:50:fc:5d:d5:fb:6f:92:e1:
+         f4:7e:f0:ae:af:f9:39:b3:ce:4b:01:9c:bd:4e:f7:f1:f2:6f:
+         ce:c0:36:d8
+-----BEGIN CERTIFICATE-----
+MIIFATCCA+mgAwIBAgIEBydvrjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEyMDQyNTE3NDEzNloX
+DTIwMDQyNTE3NDA1NVowJzElMCMGA1UEAxMcTWljcm9zb2Z0IEludGVybmV0IEF1
+dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5AVBGqKHlm
+Xqm7sJNiS3MZqukvkUxtlzclaLiwNlCUXO//xMQXdkNX/KDhxjPsMGK+NQ8jepZN
+bHRiCX4xSE6fTapbsxa3/qApL2WlS6xqVoq/KHBq3zcY3+zGhyI+Ft04HA/iBb53
+q+6Fih4NIz/pJOGQR7JnOxU+VSA8+fVC16/FZg2alBe5amd8qPqxJgAEI9VNo66N
+eWCxjoNPzbt3eCey6HStH0w0gDF/i+RQfm9/z1Xaxvu2WvVdHTiUpf6wjSJjI+dw
+QEC/iapURiUD7KTyrcBAs3L+lLXZlrIbc17V9GtHQXmk2vOOLo04TcleFxyutUw2
+a+h/2STHKPCnhr7oHQi023IjZNFC8/VM2qzztqV1aPzwuwJhXR56B1IpvnQSQlOc
+jczpiHukVTYIWIwhicW6E+htm4GKd8Y4bPU6HZE3Vyup6ytGSqiXKIujfkrdjafT
+AhB8lrDIvIYoBtZXoqdm8RfWzF9VEj1ZA6LH0tZptA0l+fHTlFYWLia8lvm6HFGb
+gfE3vHfuftmaePZBtHHfECVbsuE6x3vybLMZtyDnienEpWqkfDkk7uddKyvJkf6h
+PDMlvcVBFJLuz1Y6JhN1yhFcxierSYirVXL8ZUjcrs3xj4sQZiqQLguLt/44dc2x
+dYC2j89DHCEpkw/jE7nisMiyRipaFAwdAgMBAAGjggEAMIH9MBIGA1UdEwEB/wQI
+MAYBAf8CAQEwUwYDVR0gBEwwSjBIBgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYt
+aHR0cDovL2N5YmVydHJ1c3Qub21uaXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4G
+A1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAWgBTlnVkwgkdYzKz6CFQ2hns6tQRN8DBC
+BgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY2RwMS5wdWJsaWMtdHJ1c3QuY29tL0NS
+TC9PbW5pcm9vdDIwMjUuY3JsMB0GA1UdDgQWBBQqTZeVXTR+nbbmM76cJ8Fwfmfb
+wTANBgkqhkiG9w0BAQUFAAOCAQEAIz6HbtPK1g6wNiWNzArO3F1080Ib76rASXOF
+zH0vdwkjxSxQHUbDaGjvEDLZTX7jWuMdGDzSQFTN71nu3/77cEq82HQ3K9ykaHFQ
+umPLRN0RSP/48f9gunyqMH3dF7F36lCQIAALoz1dmHFRn90twHheDVWxg0Xe5VmY
+baThaXwy0AR796kdlzvVWb/Lb52ktqW7QRHtyJGDFVWuWTa3n2rwuDj5fDIllcwz
+8THn38t4SzYf9FXgvSj5yrlkmc7rYemBcpTTm80KKyqklIOuagwjRA41raHp7NjX
+dZCN4dazxVD8XdX7b5Lh9H7wrq/5ObPOSwGcvU738fJvzsA22A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert71[] = {
+  0x30, 0x82, 0x05, 0x01, 0x30, 0x82, 0x03, 0xe9, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x6f, 0xae, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+  0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+  0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+  0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+  0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+  0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32,
+  0x30, 0x34, 0x32, 0x35, 0x31, 0x37, 0x34, 0x31, 0x33, 0x36, 0x5a, 0x17,
+  0x0d, 0x32, 0x30, 0x30, 0x34, 0x32, 0x35, 0x31, 0x37, 0x34, 0x30, 0x35,
+  0x35, 0x5a, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+  0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x02, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02,
+  0x82, 0x02, 0x01, 0x00, 0xbe, 0x40, 0x54, 0x11, 0xaa, 0x28, 0x79, 0x66,
+  0x5e, 0xa9, 0xbb, 0xb0, 0x93, 0x62, 0x4b, 0x73, 0x19, 0xaa, 0xe9, 0x2f,
+  0x91, 0x4c, 0x6d, 0x97, 0x37, 0x25, 0x68, 0xb8, 0xb0, 0x36, 0x50, 0x94,
+  0x5c, 0xef, 0xff, 0xc4, 0xc4, 0x17, 0x76, 0x43, 0x57, 0xfc, 0xa0, 0xe1,
+  0xc6, 0x33, 0xec, 0x30, 0x62, 0xbe, 0x35, 0x0f, 0x23, 0x7a, 0x96, 0x4d,
+  0x6c, 0x74, 0x62, 0x09, 0x7e, 0x31, 0x48, 0x4e, 0x9f, 0x4d, 0xaa, 0x5b,
+  0xb3, 0x16, 0xb7, 0xfe, 0xa0, 0x29, 0x2f, 0x65, 0xa5, 0x4b, 0xac, 0x6a,
+  0x56, 0x8a, 0xbf, 0x28, 0x70, 0x6a, 0xdf, 0x37, 0x18, 0xdf, 0xec, 0xc6,
+  0x87, 0x22, 0x3e, 0x16, 0xdd, 0x38, 0x1c, 0x0f, 0xe2, 0x05, 0xbe, 0x77,
+  0xab, 0xee, 0x85, 0x8a, 0x1e, 0x0d, 0x23, 0x3f, 0xe9, 0x24, 0xe1, 0x90,
+  0x47, 0xb2, 0x67, 0x3b, 0x15, 0x3e, 0x55, 0x20, 0x3c, 0xf9, 0xf5, 0x42,
+  0xd7, 0xaf, 0xc5, 0x66, 0x0d, 0x9a, 0x94, 0x17, 0xb9, 0x6a, 0x67, 0x7c,
+  0xa8, 0xfa, 0xb1, 0x26, 0x00, 0x04, 0x23, 0xd5, 0x4d, 0xa3, 0xae, 0x8d,
+  0x79, 0x60, 0xb1, 0x8e, 0x83, 0x4f, 0xcd, 0xbb, 0x77, 0x78, 0x27, 0xb2,
+  0xe8, 0x74, 0xad, 0x1f, 0x4c, 0x34, 0x80, 0x31, 0x7f, 0x8b, 0xe4, 0x50,
+  0x7e, 0x6f, 0x7f, 0xcf, 0x55, 0xda, 0xc6, 0xfb, 0xb6, 0x5a, 0xf5, 0x5d,
+  0x1d, 0x38, 0x94, 0xa5, 0xfe, 0xb0, 0x8d, 0x22, 0x63, 0x23, 0xe7, 0x70,
+  0x40, 0x40, 0xbf, 0x89, 0xaa, 0x54, 0x46, 0x25, 0x03, 0xec, 0xa4, 0xf2,
+  0xad, 0xc0, 0x40, 0xb3, 0x72, 0xfe, 0x94, 0xb5, 0xd9, 0x96, 0xb2, 0x1b,
+  0x73, 0x5e, 0xd5, 0xf4, 0x6b, 0x47, 0x41, 0x79, 0xa4, 0xda, 0xf3, 0x8e,
+  0x2e, 0x8d, 0x38, 0x4d, 0xc9, 0x5e, 0x17, 0x1c, 0xae, 0xb5, 0x4c, 0x36,
+  0x6b, 0xe8, 0x7f, 0xd9, 0x24, 0xc7, 0x28, 0xf0, 0xa7, 0x86, 0xbe, 0xe8,
+  0x1d, 0x08, 0xb4, 0xdb, 0x72, 0x23, 0x64, 0xd1, 0x42, 0xf3, 0xf5, 0x4c,
+  0xda, 0xac, 0xf3, 0xb6, 0xa5, 0x75, 0x68, 0xfc, 0xf0, 0xbb, 0x02, 0x61,
+  0x5d, 0x1e, 0x7a, 0x07, 0x52, 0x29, 0xbe, 0x74, 0x12, 0x42, 0x53, 0x9c,
+  0x8d, 0xcc, 0xe9, 0x88, 0x7b, 0xa4, 0x55, 0x36, 0x08, 0x58, 0x8c, 0x21,
+  0x89, 0xc5, 0xba, 0x13, 0xe8, 0x6d, 0x9b, 0x81, 0x8a, 0x77, 0xc6, 0x38,
+  0x6c, 0xf5, 0x3a, 0x1d, 0x91, 0x37, 0x57, 0x2b, 0xa9, 0xeb, 0x2b, 0x46,
+  0x4a, 0xa8, 0x97, 0x28, 0x8b, 0xa3, 0x7e, 0x4a, 0xdd, 0x8d, 0xa7, 0xd3,
+  0x02, 0x10, 0x7c, 0x96, 0xb0, 0xc8, 0xbc, 0x86, 0x28, 0x06, 0xd6, 0x57,
+  0xa2, 0xa7, 0x66, 0xf1, 0x17, 0xd6, 0xcc, 0x5f, 0x55, 0x12, 0x3d, 0x59,
+  0x03, 0xa2, 0xc7, 0xd2, 0xd6, 0x69, 0xb4, 0x0d, 0x25, 0xf9, 0xf1, 0xd3,
+  0x94, 0x56, 0x16, 0x2e, 0x26, 0xbc, 0x96, 0xf9, 0xba, 0x1c, 0x51, 0x9b,
+  0x81, 0xf1, 0x37, 0xbc, 0x77, 0xee, 0x7e, 0xd9, 0x9a, 0x78, 0xf6, 0x41,
+  0xb4, 0x71, 0xdf, 0x10, 0x25, 0x5b, 0xb2, 0xe1, 0x3a, 0xc7, 0x7b, 0xf2,
+  0x6c, 0xb3, 0x19, 0xb7, 0x20, 0xe7, 0x89, 0xe9, 0xc4, 0xa5, 0x6a, 0xa4,
+  0x7c, 0x39, 0x24, 0xee, 0xe7, 0x5d, 0x2b, 0x2b, 0xc9, 0x91, 0xfe, 0xa1,
+  0x3c, 0x33, 0x25, 0xbd, 0xc5, 0x41, 0x14, 0x92, 0xee, 0xcf, 0x56, 0x3a,
+  0x26, 0x13, 0x75, 0xca, 0x11, 0x5c, 0xc6, 0x27, 0xab, 0x49, 0x88, 0xab,
+  0x55, 0x72, 0xfc, 0x65, 0x48, 0xdc, 0xae, 0xcd, 0xf1, 0x8f, 0x8b, 0x10,
+  0x66, 0x2a, 0x90, 0x2e, 0x0b, 0x8b, 0xb7, 0xfe, 0x38, 0x75, 0xcd, 0xb1,
+  0x75, 0x80, 0xb6, 0x8f, 0xcf, 0x43, 0x1c, 0x21, 0x29, 0x93, 0x0f, 0xe3,
+  0x13, 0xb9, 0xe2, 0xb0, 0xc8, 0xb2, 0x46, 0x2a, 0x5a, 0x14, 0x0c, 0x1d,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x00, 0x30, 0x81, 0xfd,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x53, 0x06, 0x03,
+  0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b,
+  0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f,
+  0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+  0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x86, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa,
+  0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0,
+  0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52,
+  0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30,
+  0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x2a, 0x4d, 0x97, 0x95, 0x5d, 0x34, 0x7e,
+  0x9d, 0xb6, 0xe6, 0x33, 0xbe, 0x9c, 0x27, 0xc1, 0x70, 0x7e, 0x67, 0xdb,
+  0xc1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x23, 0x3e, 0x87,
+  0x6e, 0xd3, 0xca, 0xd6, 0x0e, 0xb0, 0x36, 0x25, 0x8d, 0xcc, 0x0a, 0xce,
+  0xdc, 0x5d, 0x74, 0xf3, 0x42, 0x1b, 0xef, 0xaa, 0xc0, 0x49, 0x73, 0x85,
+  0xcc, 0x7d, 0x2f, 0x77, 0x09, 0x23, 0xc5, 0x2c, 0x50, 0x1d, 0x46, 0xc3,
+  0x68, 0x68, 0xef, 0x10, 0x32, 0xd9, 0x4d, 0x7e, 0xe3, 0x5a, 0xe3, 0x1d,
+  0x18, 0x3c, 0xd2, 0x40, 0x54, 0xcd, 0xef, 0x59, 0xee, 0xdf, 0xfe, 0xfb,
+  0x70, 0x4a, 0xbc, 0xd8, 0x74, 0x37, 0x2b, 0xdc, 0xa4, 0x68, 0x71, 0x50,
+  0xba, 0x63, 0xcb, 0x44, 0xdd, 0x11, 0x48, 0xff, 0xf8, 0xf1, 0xff, 0x60,
+  0xba, 0x7c, 0xaa, 0x30, 0x7d, 0xdd, 0x17, 0xb1, 0x77, 0xea, 0x50, 0x90,
+  0x20, 0x00, 0x0b, 0xa3, 0x3d, 0x5d, 0x98, 0x71, 0x51, 0x9f, 0xdd, 0x2d,
+  0xc0, 0x78, 0x5e, 0x0d, 0x55, 0xb1, 0x83, 0x45, 0xde, 0xe5, 0x59, 0x98,
+  0x6d, 0xa4, 0xe1, 0x69, 0x7c, 0x32, 0xd0, 0x04, 0x7b, 0xf7, 0xa9, 0x1d,
+  0x97, 0x3b, 0xd5, 0x59, 0xbf, 0xcb, 0x6f, 0x9d, 0xa4, 0xb6, 0xa5, 0xbb,
+  0x41, 0x11, 0xed, 0xc8, 0x91, 0x83, 0x15, 0x55, 0xae, 0x59, 0x36, 0xb7,
+  0x9f, 0x6a, 0xf0, 0xb8, 0x38, 0xf9, 0x7c, 0x32, 0x25, 0x95, 0xcc, 0x33,
+  0xf1, 0x31, 0xe7, 0xdf, 0xcb, 0x78, 0x4b, 0x36, 0x1f, 0xf4, 0x55, 0xe0,
+  0xbd, 0x28, 0xf9, 0xca, 0xb9, 0x64, 0x99, 0xce, 0xeb, 0x61, 0xe9, 0x81,
+  0x72, 0x94, 0xd3, 0x9b, 0xcd, 0x0a, 0x2b, 0x2a, 0xa4, 0x94, 0x83, 0xae,
+  0x6a, 0x0c, 0x23, 0x44, 0x0e, 0x35, 0xad, 0xa1, 0xe9, 0xec, 0xd8, 0xd7,
+  0x75, 0x90, 0x8d, 0xe1, 0xd6, 0xb3, 0xc5, 0x50, 0xfc, 0x5d, 0xd5, 0xfb,
+  0x6f, 0x92, 0xe1, 0xf4, 0x7e, 0xf0, 0xae, 0xaf, 0xf9, 0x39, 0xb3, 0xce,
+  0x4b, 0x01, 0x9c, 0xbd, 0x4e, 0xf7, 0xf1, 0xf2, 0x6f, 0xce, 0xc0, 0x36,
+  0xd8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            20:93:42:da:a7:64:7c:05:c5:f5:fd:93:76:a4:42:8c
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Validity
+            Not Before: Jan 17 00:00:00 2007 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=RU, O=RBC Hosting Center, CN=RBC HC High Assurance Services CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c7:16:e6:21:49:a0:b4:13:ca:18:12:2c:a8:aa:
+                    fa:46:9c:d1:5b:5a:1d:26:a3:e6:11:68:3b:02:00:
+                    ea:a5:be:95:f7:4c:d7:a1:bc:70:9a:a2:ec:9f:de:
+                    d1:b3:1a:c8:16:e0:68:cf:8a:23:3a:6b:1b:59:94:
+                    49:7d:60:cf:51:c6:e3:66:45:f9:6a:5d:f7:88:e9:
+                    a1:b1:ab:96:17:3c:46:72:a9:4f:8b:a0:58:cb:bf:
+                    c3:9d:91:c4:67:c6:33:ef:fb:16:8b:94:0a:0a:a6:
+                    52:b9:fe:c5:3e:a4:37:c5:ca:ce:b1:bf:a2:db:49:
+                    63:3e:cd:21:75:fa:28:8f:e0:67:4e:32:b2:c3:f3:
+                    ea:b2:8b:e5:6a:93:2d:90:dd:19:45:12:f3:ce:16:
+                    7d:1c:ca:b1:93:c0:02:41:97:d0:b7:fc:ac:4f:86:
+                    2c:61:de:cd:ad:ac:4f:19:6b:e2:9e:91:3e:9b:3d:
+                    e5:ed:4f:df:d1:10:59:c4:18:f5:25:2c:de:73:ad:
+                    7b:25:31:b5:5d:c5:91:95:f3:6b:9f:fa:d2:b8:ad:
+                    23:7c:9e:45:86:72:aa:09:c0:86:29:e4:75:63:5b:
+                    7d:87:77:d2:89:51:91:27:3a:08:97:25:0d:83:21:
+                    9f:a7:39:fc:43:41:74:c9:ed:c7:00:9d:eb:3c:41:
+                    f7:d5
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+            X509v3 Subject Key Identifier: 
+                66:8F:F1:9F:4D:DB:8E:DD:FA:EC:1B:99:13:6A:B8:82:68:85:06:ED
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6449.1.2.2.16
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/UTNAddTrustServerCA.crt
+                CA Issuers - URI:http://crt.comodo.net/UTNAddTrustServerCA.crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         65:2c:c5:19:68:1d:3c:1c:e9:15:9f:a5:b4:fe:32:19:d0:76:
+         f3:b3:f8:58:b5:10:e1:4e:44:f3:c1:e1:b5:98:81:41:69:d7:
+         89:d4:05:49:e3:15:30:4a:24:c4:1e:67:e7:3d:ab:99:b9:85:
+         91:04:2f:15:3e:42:a6:e6:e6:25:ac:6e:30:91:3f:03:3f:78:
+         f9:2f:ad:15:fa:e6:ad:1e:57:ea:7b:cc:e4:ce:3a:89:5d:3d:
+         89:d1:ba:8a:fa:2d:f5:eb:a9:31:95:50:06:f4:34:f3:95:a6:
+         9b:43:41:20:65:3b:ab:85:d7:e0:53:53:df:54:89:f7:10:c1:
+         67:79:b0:2e:a2:8b:c8:cd:02:c7:92:5d:e1:4c:e7:d1:87:bb:
+         8f:82:ee:ce:b7:0c:7c:68:76:da:f3:1f:e5:96:71:e8:77:97:
+         34:06:13:55:28:81:58:40:75:74:47:28:97:14:0a:2b:53:b0:
+         eb:6e:42:68:76:ef:67:21:61:b6:64:74:08:70:6d:11:c6:e7:
+         7f:e5:36:1c:66:3a:fd:4e:7a:63:9e:15:ce:cf:a9:04:e6:30:
+         ea:10:d2:5a:81:24:4b:c2:f2:3b:fb:34:e6:69:7b:ff:5d:9b:
+         30:46:59:69:2d:0d:f2:7f:fc:fb:0a:29:ea:35:14:d0:bb:3c:
+         a3:b1:10:41
+-----BEGIN CERTIFICATE-----
+MIIFAjCCA+qgAwIBAgIQIJNC2qdkfAXF9f2TdqRCjDANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDcwMTE3MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBWMQswCQYD
+VQQGEwJSVTEbMBkGA1UEChMSUkJDIEhvc3RpbmcgQ2VudGVyMSowKAYDVQQDEyFS
+QkMgSEMgSGlnaCBBc3N1cmFuY2UgU2VydmljZXMgQ0EwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDHFuYhSaC0E8oYEiyoqvpGnNFbWh0mo+YRaDsCAOql
+vpX3TNehvHCaouyf3tGzGsgW4GjPiiM6axtZlEl9YM9RxuNmRflqXfeI6aGxq5YX
+PEZyqU+LoFjLv8OdkcRnxjPv+xaLlAoKplK5/sU+pDfFys6xv6LbSWM+zSF1+iiP
+4GdOMrLD8+qyi+Vqky2Q3RlFEvPOFn0cyrGTwAJBl9C3/KxPhixh3s2trE8Za+Ke
+kT6bPeXtT9/REFnEGPUlLN5zrXslMbVdxZGV82uf+tK4rSN8nkWGcqoJwIYp5HVj
+W32Hd9KJUZEnOgiXJQ2DIZ+nOfxDQXTJ7ccAnes8QffVAgMBAAGjggGIMIIBhDAf
+BgNVHSMEGDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNVHQ4EFgQUZo/xn03b
+jt367BuZE2q4gmiFBu0wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwGAYDVR0gBBEwDzANBgsrBgEEAbIxAQICEDB7BgNVHR8EdDByMDigNqA0hjJo
+dHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDA2oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L1VUTi1VU0VSRmlyc3QtSGFy
+ZHdhcmUuY3JsMIGGBggrBgEFBQcBAQR6MHgwOwYIKwYBBQUHMAKGL2h0dHA6Ly9j
+cnQuY29tb2RvY2EuY29tL1VUTkFkZFRydXN0U2VydmVyQ0EuY3J0MDkGCCsGAQUF
+BzAChi1odHRwOi8vY3J0LmNvbW9kby5uZXQvVVROQWRkVHJ1c3RTZXJ2ZXJDQS5j
+cnQwDQYJKoZIhvcNAQEFBQADggEBAGUsxRloHTwc6RWfpbT+MhnQdvOz+Fi1EOFO
+RPPB4bWYgUFp14nUBUnjFTBKJMQeZ+c9q5m5hZEELxU+Qqbm5iWsbjCRPwM/ePkv
+rRX65q0eV+p7zOTOOoldPYnRuor6LfXrqTGVUAb0NPOVpptDQSBlO6uF1+BTU99U
+ifcQwWd5sC6ii8jNAseSXeFM59GHu4+C7s63DHxodtrzH+WWceh3lzQGE1UogVhA
+dXRHKJcUCitTsOtuQmh272chYbZkdAhwbRHG53/lNhxmOv1OemOeFc7PqQTmMOoQ
+0lqBJEvC8jv7NOZpe/9dmzBGWWktDfJ//PsKKeo1FNC7PKOxEEE=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert72[] = {
+  0x30, 0x82, 0x05, 0x02, 0x30, 0x82, 0x03, 0xea, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x20, 0x93, 0x42, 0xda, 0xa7, 0x64, 0x7c, 0x05, 0xc5,
+  0xf5, 0xfd, 0x93, 0x76, 0xa4, 0x42, 0x8c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+  0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+  0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x37, 0x30, 0x31, 0x31, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+  0x38, 0x33, 0x38, 0x5a, 0x30, 0x56, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x52, 0x55, 0x31, 0x1b, 0x30, 0x19, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x12, 0x52, 0x42, 0x43, 0x20, 0x48, 0x6f,
+  0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72,
+  0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x52,
+  0x42, 0x43, 0x20, 0x48, 0x43, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+  0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72,
+  0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xc7, 0x16, 0xe6, 0x21, 0x49, 0xa0, 0xb4,
+  0x13, 0xca, 0x18, 0x12, 0x2c, 0xa8, 0xaa, 0xfa, 0x46, 0x9c, 0xd1, 0x5b,
+  0x5a, 0x1d, 0x26, 0xa3, 0xe6, 0x11, 0x68, 0x3b, 0x02, 0x00, 0xea, 0xa5,
+  0xbe, 0x95, 0xf7, 0x4c, 0xd7, 0xa1, 0xbc, 0x70, 0x9a, 0xa2, 0xec, 0x9f,
+  0xde, 0xd1, 0xb3, 0x1a, 0xc8, 0x16, 0xe0, 0x68, 0xcf, 0x8a, 0x23, 0x3a,
+  0x6b, 0x1b, 0x59, 0x94, 0x49, 0x7d, 0x60, 0xcf, 0x51, 0xc6, 0xe3, 0x66,
+  0x45, 0xf9, 0x6a, 0x5d, 0xf7, 0x88, 0xe9, 0xa1, 0xb1, 0xab, 0x96, 0x17,
+  0x3c, 0x46, 0x72, 0xa9, 0x4f, 0x8b, 0xa0, 0x58, 0xcb, 0xbf, 0xc3, 0x9d,
+  0x91, 0xc4, 0x67, 0xc6, 0x33, 0xef, 0xfb, 0x16, 0x8b, 0x94, 0x0a, 0x0a,
+  0xa6, 0x52, 0xb9, 0xfe, 0xc5, 0x3e, 0xa4, 0x37, 0xc5, 0xca, 0xce, 0xb1,
+  0xbf, 0xa2, 0xdb, 0x49, 0x63, 0x3e, 0xcd, 0x21, 0x75, 0xfa, 0x28, 0x8f,
+  0xe0, 0x67, 0x4e, 0x32, 0xb2, 0xc3, 0xf3, 0xea, 0xb2, 0x8b, 0xe5, 0x6a,
+  0x93, 0x2d, 0x90, 0xdd, 0x19, 0x45, 0x12, 0xf3, 0xce, 0x16, 0x7d, 0x1c,
+  0xca, 0xb1, 0x93, 0xc0, 0x02, 0x41, 0x97, 0xd0, 0xb7, 0xfc, 0xac, 0x4f,
+  0x86, 0x2c, 0x61, 0xde, 0xcd, 0xad, 0xac, 0x4f, 0x19, 0x6b, 0xe2, 0x9e,
+  0x91, 0x3e, 0x9b, 0x3d, 0xe5, 0xed, 0x4f, 0xdf, 0xd1, 0x10, 0x59, 0xc4,
+  0x18, 0xf5, 0x25, 0x2c, 0xde, 0x73, 0xad, 0x7b, 0x25, 0x31, 0xb5, 0x5d,
+  0xc5, 0x91, 0x95, 0xf3, 0x6b, 0x9f, 0xfa, 0xd2, 0xb8, 0xad, 0x23, 0x7c,
+  0x9e, 0x45, 0x86, 0x72, 0xaa, 0x09, 0xc0, 0x86, 0x29, 0xe4, 0x75, 0x63,
+  0x5b, 0x7d, 0x87, 0x77, 0xd2, 0x89, 0x51, 0x91, 0x27, 0x3a, 0x08, 0x97,
+  0x25, 0x0d, 0x83, 0x21, 0x9f, 0xa7, 0x39, 0xfc, 0x43, 0x41, 0x74, 0xc9,
+  0xed, 0xc7, 0x00, 0x9d, 0xeb, 0x3c, 0x41, 0xf7, 0xd5, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x88, 0x30, 0x82, 0x01, 0x84, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xa1,
+  0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5,
+  0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x66, 0x8f, 0xf1, 0x9f, 0x4d, 0xdb,
+  0x8e, 0xdd, 0xfa, 0xec, 0x1b, 0x99, 0x13, 0x6a, 0xb8, 0x82, 0x68, 0x85,
+  0x06, 0xed, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+  0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+  0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x11, 0x30,
+  0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31,
+  0x01, 0x02, 0x02, 0x10, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+  0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f,
+  0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55,
+  0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+  0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+  0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55,
+  0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72,
+  0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x86,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x7a,
+  0x30, 0x78, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x02, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e,
+  0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63,
+  0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x65, 0x2c,
+  0xc5, 0x19, 0x68, 0x1d, 0x3c, 0x1c, 0xe9, 0x15, 0x9f, 0xa5, 0xb4, 0xfe,
+  0x32, 0x19, 0xd0, 0x76, 0xf3, 0xb3, 0xf8, 0x58, 0xb5, 0x10, 0xe1, 0x4e,
+  0x44, 0xf3, 0xc1, 0xe1, 0xb5, 0x98, 0x81, 0x41, 0x69, 0xd7, 0x89, 0xd4,
+  0x05, 0x49, 0xe3, 0x15, 0x30, 0x4a, 0x24, 0xc4, 0x1e, 0x67, 0xe7, 0x3d,
+  0xab, 0x99, 0xb9, 0x85, 0x91, 0x04, 0x2f, 0x15, 0x3e, 0x42, 0xa6, 0xe6,
+  0xe6, 0x25, 0xac, 0x6e, 0x30, 0x91, 0x3f, 0x03, 0x3f, 0x78, 0xf9, 0x2f,
+  0xad, 0x15, 0xfa, 0xe6, 0xad, 0x1e, 0x57, 0xea, 0x7b, 0xcc, 0xe4, 0xce,
+  0x3a, 0x89, 0x5d, 0x3d, 0x89, 0xd1, 0xba, 0x8a, 0xfa, 0x2d, 0xf5, 0xeb,
+  0xa9, 0x31, 0x95, 0x50, 0x06, 0xf4, 0x34, 0xf3, 0x95, 0xa6, 0x9b, 0x43,
+  0x41, 0x20, 0x65, 0x3b, 0xab, 0x85, 0xd7, 0xe0, 0x53, 0x53, 0xdf, 0x54,
+  0x89, 0xf7, 0x10, 0xc1, 0x67, 0x79, 0xb0, 0x2e, 0xa2, 0x8b, 0xc8, 0xcd,
+  0x02, 0xc7, 0x92, 0x5d, 0xe1, 0x4c, 0xe7, 0xd1, 0x87, 0xbb, 0x8f, 0x82,
+  0xee, 0xce, 0xb7, 0x0c, 0x7c, 0x68, 0x76, 0xda, 0xf3, 0x1f, 0xe5, 0x96,
+  0x71, 0xe8, 0x77, 0x97, 0x34, 0x06, 0x13, 0x55, 0x28, 0x81, 0x58, 0x40,
+  0x75, 0x74, 0x47, 0x28, 0x97, 0x14, 0x0a, 0x2b, 0x53, 0xb0, 0xeb, 0x6e,
+  0x42, 0x68, 0x76, 0xef, 0x67, 0x21, 0x61, 0xb6, 0x64, 0x74, 0x08, 0x70,
+  0x6d, 0x11, 0xc6, 0xe7, 0x7f, 0xe5, 0x36, 0x1c, 0x66, 0x3a, 0xfd, 0x4e,
+  0x7a, 0x63, 0x9e, 0x15, 0xce, 0xcf, 0xa9, 0x04, 0xe6, 0x30, 0xea, 0x10,
+  0xd2, 0x5a, 0x81, 0x24, 0x4b, 0xc2, 0xf2, 0x3b, 0xfb, 0x34, 0xe6, 0x69,
+  0x7b, 0xff, 0x5d, 0x9b, 0x30, 0x46, 0x59, 0x69, 0x2d, 0x0d, 0xf2, 0x7f,
+  0xfc, 0xfb, 0x0a, 0x29, 0xea, 0x35, 0x14, 0xd0, 0xbb, 0x3c, 0xa3, 0xb1,
+  0x10, 0x41,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            18:b2:cb:ba:a3:04:f1:a0:0f:c1:f2:f3:26:46:2a:4a
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Validity
+            Not Before: Dec  1 00:00:00 2006 GMT
+            Not After : Dec 31 23:59:59 2019 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=EssentialSSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ad:f0:08:b0:72:c6:ab:83:12:31:17:70:89:85:
+                    a9:20:12:d4:98:6a:ed:80:d4:d1:df:e4:8e:59:2d:
+                    d3:96:21:8d:76:d2:3f:18:0b:46:19:63:0b:c7:20:
+                    f3:e5:0b:dd:80:1a:f1:5a:a0:bd:1d:76:cd:b7:23:
+                    3a:74:5e:61:1b:75:aa:9b:d4:85:f4:e1:78:91:d3:
+                    2d:e1:af:fc:98:2e:06:d2:79:3d:5a:c0:1f:21:2d:
+                    1c:ae:21:53:c6:3a:a7:21:7e:be:ed:67:6f:75:1d:
+                    1a:9f:6a:5b:06:b3:6a:e3:b1:0b:aa:6a:0e:e7:6d:
+                    6c:c3:ca:95:8c:37:ce:21:1f:35:90:7d:db:da:1a:
+                    5c:a8:88:14:b2:0f:c8:12:20:5f:c5:d3:7f:e8:e1:
+                    38:e0:db:bc:f9:1f:a1:aa:d6:1b:90:07:21:fa:45:
+                    24:50:5d:27:2a:a0:28:41:45:5b:7d:bc:a0:a2:2f:
+                    aa:9b:7e:5b:53:c5:f1:05:16:57:7e:11:d7:3b:b4:
+                    d9:01:76:dc:df:7d:10:cf:51:a9:e5:38:f2:7b:14:
+                    00:75:59:f9:f0:59:db:17:3e:f7:af:e6:02:2d:a4:
+                    79:c1:5d:a2:1c:c3:9a:c8:a7:a8:0b:48:0a:6a:2e:
+                    7f:2d:97:65:f6:c5:04:9c:44:c8:99:96:7e:7e:a4:
+                    dd:2f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+            X509v3 Subject Key Identifier: 
+                DA:CB:EA:AD:5B:08:5D:CC:FF:FC:26:54:CE:49:E5:55:C6:38:F4:F8
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://secure.comodo.com/CPS
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/ComodoUTNSGCCA.crt
+                OCSP - URI:http://ocsp.comodoca.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         2d:97:34:7a:40:32:ea:70:97:2f:81:3b:4b:79:12:77:ae:fb:
+         aa:d7:1a:a8:da:5f:f3:a1:db:9e:4d:96:cb:37:7a:a8:ea:ee:
+         9b:95:db:9d:bb:e1:27:9e:fd:45:ed:0e:52:96:ac:f4:27:bf:
+         74:aa:92:f4:a5:c4:43:00:1f:0e:b5:78:f9:8a:c5:8c:70:bd:
+         9a:7a:31:a3:29:d0:59:6b:4c:33:b5:2c:f8:8b:0f:92:63:57:
+         56:ac:24:67:8a:5b:2f:29:c2:b1:b9:da:24:c5:e4:62:0e:7e:
+         79:c3:fe:b9:83:ea:27:3b:bc:1d:43:b5:6e:17:aa:fb:c8:98:
+         88:6a:d9:f2:7c:a1:f6:71:ba:19:4f:b8:38:e3:42:d7:f0:da:
+         b1:c0:23:df:dd:d7:f1:a7:ed:09:8f:56:a0:ab:c3:0b:cb:a4:
+         92:80:81:92:1f:a9:6f:f9:6c:33:dc:3e:57:c6:a7:f2:1f:cc:
+         2a:7c:e4:2c:4c:46:5f:eb:f3:61:f7:2b:c4:35:9f:8d:58:f5:
+         3a:83:44:0e:d8:93:ac:4c:6b:cc:77:f4:03:cd:cc:dc:e0:1c:
+         4b:5d:25:da:3d:5e:ce:77:8a:e1:3e:c6:d7:94:cd:70:49:3c:
+         ff:0e:bd:08:48:ab:e5:52:14:15:9d:0e:9c:1a:87:56:68:ad:
+         9c:09:00:64
+-----BEGIN CERTIFICATE-----
+MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh
+dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E
+TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf
+5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR
+0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD
+ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq
+oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX
+Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD
+MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU
+2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud
+IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v
+ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh
+LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB
+AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k
+b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z
+odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a
+ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu
+F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv
++Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL
+XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert73[] = {
+  0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x18, 0xb2, 0xcb, 0xba, 0xa3, 0x04, 0xf1, 0xa0, 0x0f,
+  0xc1, 0xf2, 0xf3, 0x26, 0x46, 0x2a, 0x4a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+  0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x72, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30,
+  0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61,
+  0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74,
+  0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
+  0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+  0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
+  0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0f, 0x45,
+  0x73, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xad,
+  0xf0, 0x08, 0xb0, 0x72, 0xc6, 0xab, 0x83, 0x12, 0x31, 0x17, 0x70, 0x89,
+  0x85, 0xa9, 0x20, 0x12, 0xd4, 0x98, 0x6a, 0xed, 0x80, 0xd4, 0xd1, 0xdf,
+  0xe4, 0x8e, 0x59, 0x2d, 0xd3, 0x96, 0x21, 0x8d, 0x76, 0xd2, 0x3f, 0x18,
+  0x0b, 0x46, 0x19, 0x63, 0x0b, 0xc7, 0x20, 0xf3, 0xe5, 0x0b, 0xdd, 0x80,
+  0x1a, 0xf1, 0x5a, 0xa0, 0xbd, 0x1d, 0x76, 0xcd, 0xb7, 0x23, 0x3a, 0x74,
+  0x5e, 0x61, 0x1b, 0x75, 0xaa, 0x9b, 0xd4, 0x85, 0xf4, 0xe1, 0x78, 0x91,
+  0xd3, 0x2d, 0xe1, 0xaf, 0xfc, 0x98, 0x2e, 0x06, 0xd2, 0x79, 0x3d, 0x5a,
+  0xc0, 0x1f, 0x21, 0x2d, 0x1c, 0xae, 0x21, 0x53, 0xc6, 0x3a, 0xa7, 0x21,
+  0x7e, 0xbe, 0xed, 0x67, 0x6f, 0x75, 0x1d, 0x1a, 0x9f, 0x6a, 0x5b, 0x06,
+  0xb3, 0x6a, 0xe3, 0xb1, 0x0b, 0xaa, 0x6a, 0x0e, 0xe7, 0x6d, 0x6c, 0xc3,
+  0xca, 0x95, 0x8c, 0x37, 0xce, 0x21, 0x1f, 0x35, 0x90, 0x7d, 0xdb, 0xda,
+  0x1a, 0x5c, 0xa8, 0x88, 0x14, 0xb2, 0x0f, 0xc8, 0x12, 0x20, 0x5f, 0xc5,
+  0xd3, 0x7f, 0xe8, 0xe1, 0x38, 0xe0, 0xdb, 0xbc, 0xf9, 0x1f, 0xa1, 0xaa,
+  0xd6, 0x1b, 0x90, 0x07, 0x21, 0xfa, 0x45, 0x24, 0x50, 0x5d, 0x27, 0x2a,
+  0xa0, 0x28, 0x41, 0x45, 0x5b, 0x7d, 0xbc, 0xa0, 0xa2, 0x2f, 0xaa, 0x9b,
+  0x7e, 0x5b, 0x53, 0xc5, 0xf1, 0x05, 0x16, 0x57, 0x7e, 0x11, 0xd7, 0x3b,
+  0xb4, 0xd9, 0x01, 0x76, 0xdc, 0xdf, 0x7d, 0x10, 0xcf, 0x51, 0xa9, 0xe5,
+  0x38, 0xf2, 0x7b, 0x14, 0x00, 0x75, 0x59, 0xf9, 0xf0, 0x59, 0xdb, 0x17,
+  0x3e, 0xf7, 0xaf, 0xe6, 0x02, 0x2d, 0xa4, 0x79, 0xc1, 0x5d, 0xa2, 0x1c,
+  0xc3, 0x9a, 0xc8, 0xa7, 0xa8, 0x0b, 0x48, 0x0a, 0x6a, 0x2e, 0x7f, 0x2d,
+  0x97, 0x65, 0xf6, 0xc5, 0x04, 0x9c, 0x44, 0xc8, 0x99, 0x96, 0x7e, 0x7e,
+  0xa4, 0xdd, 0x2f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x83,
+  0x30, 0x82, 0x01, 0x7f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+  0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+  0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0xda, 0xcb, 0xea, 0xad, 0x5b, 0x08, 0x5d, 0xcc, 0xff, 0xfc, 0x26, 0x54,
+  0xce, 0x49, 0xe5, 0x55, 0xc6, 0x38, 0xf4, 0xf8, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20,
+  0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+  0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+  0x64, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0,
+  0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x6c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+  0x01, 0x04, 0x60, 0x30, 0x5e, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+  0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+  0x6f, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72,
+  0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+  0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x2d,
+  0x97, 0x34, 0x7a, 0x40, 0x32, 0xea, 0x70, 0x97, 0x2f, 0x81, 0x3b, 0x4b,
+  0x79, 0x12, 0x77, 0xae, 0xfb, 0xaa, 0xd7, 0x1a, 0xa8, 0xda, 0x5f, 0xf3,
+  0xa1, 0xdb, 0x9e, 0x4d, 0x96, 0xcb, 0x37, 0x7a, 0xa8, 0xea, 0xee, 0x9b,
+  0x95, 0xdb, 0x9d, 0xbb, 0xe1, 0x27, 0x9e, 0xfd, 0x45, 0xed, 0x0e, 0x52,
+  0x96, 0xac, 0xf4, 0x27, 0xbf, 0x74, 0xaa, 0x92, 0xf4, 0xa5, 0xc4, 0x43,
+  0x00, 0x1f, 0x0e, 0xb5, 0x78, 0xf9, 0x8a, 0xc5, 0x8c, 0x70, 0xbd, 0x9a,
+  0x7a, 0x31, 0xa3, 0x29, 0xd0, 0x59, 0x6b, 0x4c, 0x33, 0xb5, 0x2c, 0xf8,
+  0x8b, 0x0f, 0x92, 0x63, 0x57, 0x56, 0xac, 0x24, 0x67, 0x8a, 0x5b, 0x2f,
+  0x29, 0xc2, 0xb1, 0xb9, 0xda, 0x24, 0xc5, 0xe4, 0x62, 0x0e, 0x7e, 0x79,
+  0xc3, 0xfe, 0xb9, 0x83, 0xea, 0x27, 0x3b, 0xbc, 0x1d, 0x43, 0xb5, 0x6e,
+  0x17, 0xaa, 0xfb, 0xc8, 0x98, 0x88, 0x6a, 0xd9, 0xf2, 0x7c, 0xa1, 0xf6,
+  0x71, 0xba, 0x19, 0x4f, 0xb8, 0x38, 0xe3, 0x42, 0xd7, 0xf0, 0xda, 0xb1,
+  0xc0, 0x23, 0xdf, 0xdd, 0xd7, 0xf1, 0xa7, 0xed, 0x09, 0x8f, 0x56, 0xa0,
+  0xab, 0xc3, 0x0b, 0xcb, 0xa4, 0x92, 0x80, 0x81, 0x92, 0x1f, 0xa9, 0x6f,
+  0xf9, 0x6c, 0x33, 0xdc, 0x3e, 0x57, 0xc6, 0xa7, 0xf2, 0x1f, 0xcc, 0x2a,
+  0x7c, 0xe4, 0x2c, 0x4c, 0x46, 0x5f, 0xeb, 0xf3, 0x61, 0xf7, 0x2b, 0xc4,
+  0x35, 0x9f, 0x8d, 0x58, 0xf5, 0x3a, 0x83, 0x44, 0x0e, 0xd8, 0x93, 0xac,
+  0x4c, 0x6b, 0xcc, 0x77, 0xf4, 0x03, 0xcd, 0xcc, 0xdc, 0xe0, 0x1c, 0x4b,
+  0x5d, 0x25, 0xda, 0x3d, 0x5e, 0xce, 0x77, 0x8a, 0xe1, 0x3e, 0xc6, 0xd7,
+  0x94, 0xcd, 0x70, 0x49, 0x3c, 0xff, 0x0e, 0xbd, 0x08, 0x48, 0xab, 0xe5,
+  0x52, 0x14, 0x15, 0x9d, 0x0e, 0x9c, 0x1a, 0x87, 0x56, 0x68, 0xad, 0x9c,
+  0x09, 0x00, 0x64,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            4c:cd:4a:9a:5b:45:13:21:8c:cf:90:2f:8b:2b:51:71
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+        Validity
+            Not Before: Sep 18 00:00:00 2006 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=PositiveSSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bd:4f:79:58:22:93:c9:28:3e:52:11:00:2f:c0:
+                    a9:20:8a:d7:2d:55:1e:10:e7:b8:7f:e2:86:2a:a4:
+                    ec:5e:e9:e4:92:58:96:54:0d:f3:17:ba:41:9e:15:
+                    91:55:ef:c2:ee:00:20:18:45:83:26:df:20:cc:3d:
+                    b3:a1:13:31:0a:21:5c:79:83:79:ab:24:15:5c:56:
+                    f0:b4:95:98:a1:da:d2:1b:ea:16:b5:cb:b7:b0:c1:
+                    53:f9:a4:46:da:f0:2e:24:b8:62:9e:8e:8b:5e:2b:
+                    5a:96:a0:ea:50:e9:88:fb:28:2a:4d:9a:9c:48:4f:
+                    83:b6:87:ae:4f:c5:c8:b5:d9:fd:be:3f:d1:a7:9d:
+                    c6:2c:13:0d:c0:01:c7:b3:70:f3:8f:69:bb:b0:3c:
+                    10:df:eb:09:00:84:3f:6e:ef:fc:e3:2d:b4:c7:5d:
+                    11:cc:f7:f2:f1:f6:e2:e3:00:7e:12:0e:de:8d:7d:
+                    00:ca:3a:3d:f6:72:e8:79:25:a6:08:16:f7:ab:88:
+                    ff:56:5e:09:17:c0:5a:82:05:62:34:2b:28:48:32:
+                    97:d0:84:0c:ac:13:18:db:9e:66:c7:aa:14:8b:11:
+                    69:4d:f1:09:d3:ba:5d:a9:88:37:62:d8:bf:03:9b:
+                    9d:d7:e6:05:7e:c2:6a:52:aa:8d:07:80:ea:8e:0e:
+                    f3:1d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+            X509v3 Subject Key Identifier: 
+                B8:CA:11:E9:06:31:79:DB:C3:94:C6:E8:19:2A:BC:BB:35:16:31:A4
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+                Full Name:
+                  URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/UTNAddTrustServerCA.crt
+                CA Issuers - URI:http://crt.comodo.net/UTNAddTrustServerCA.crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         1d:b4:e7:f9:18:48:5d:ed:fa:5a:c3:1d:e3:b7:ef:86:15:c9:
+         6c:13:49:16:0d:31:8c:31:5c:e7:da:87:64:8e:af:12:16:a4:
+         5a:2d:92:1a:3b:3e:21:65:aa:17:c3:95:7e:40:1c:fd:14:69:
+         06:18:cd:ac:31:ec:6a:f1:16:9e:89:26:3d:5b:21:8d:e8:e8:
+         f9:ab:3d:aa:3c:cb:99:f2:86:5f:88:53:15:05:17:e5:a8:d2:
+         85:a7:4e:49:7d:d6:dd:c7:8f:ce:88:bf:65:05:e0:14:b5:31:
+         77:ee:bd:2a:86:e8:e8:a7:6a:2d:da:65:19:8c:67:e2:6d:f6:
+         4a:85:66:83:b6:32:4d:9f:42:23:17:d7:45:41:6a:76:04:d4:
+         ad:b9:8f:6e:dc:c2:3e:e9:51:f2:9e:d8:f3:7f:fb:50:2a:f0:
+         8b:fd:6f:0d:22:36:2e:ce:0e:46:f2:de:8f:da:3b:7d:1e:93:
+         ac:fc:f4:32:2b:0f:ab:01:1f:a5:40:8f:e3:24:99:1f:5d:b2:
+         aa:0c:b9:e2:a1:e7:92:0c:90:4b:53:7d:1f:28:ee:56:3d:af:
+         18:67:49:df:d5:1e:c2:a8:98:2b:4c:47:83:81:4c:2e:44:c2:
+         ef:c8:63:ee:8b:7b:5b:31:f6:26:61:bf:79:1c:b0:a9:4e:a9:
+         c9:50:7b:e2
+-----BEGIN CERTIFICATE-----
+MIIFAzCCA+ugAwIBAgIQTM1KmltFEyGMz5AviytRcTANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYwOTE4MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBxMQswCQYD
+VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT
+YWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UEAxMOUG9z
+aXRpdmVTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9T3lY
+IpPJKD5SEQAvwKkgitctVR4Q57h/4oYqpOxe6eSSWJZUDfMXukGeFZFV78LuACAY
+RYMm3yDMPbOhEzEKIVx5g3mrJBVcVvC0lZih2tIb6ha1y7ewwVP5pEba8C4kuGKe
+joteK1qWoOpQ6Yj7KCpNmpxIT4O2h65Pxci12f2+P9GnncYsEw3AAcezcPOPabuw
+PBDf6wkAhD9u7/zjLbTHXRHM9/Lx9uLjAH4SDt6NfQDKOj32cuh5JaYIFveriP9W
+XgkXwFqCBWI0KyhIMpfQhAysExjbnmbHqhSLEWlN8QnTul2piDdi2L8Dm53X5gV+
+wmpSqo0HgOqODvMdAgMBAAGjggFuMIIBajAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd
+BzfVhZadS9LDRTAdBgNVHQ4EFgQUuMoR6QYxedvDlMboGSq8uzUWMaQwDgYDVR0P
+AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwewYDVR0fBHQwcjA4oDagNIYy
+aHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5j
+cmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9VVE4tVVNFUkZpcnN0LUhh
+cmR3YXJlLmNybDCBhgYIKwYBBQUHAQEEejB4MDsGCCsGAQUFBzAChi9odHRwOi8v
+Y3J0LmNvbW9kb2NhLmNvbS9VVE5BZGRUcnVzdFNlcnZlckNBLmNydDA5BggrBgEF
+BQcwAoYtaHR0cDovL2NydC5jb21vZG8ubmV0L1VUTkFkZFRydXN0U2VydmVyQ0Eu
+Y3J0MA0GCSqGSIb3DQEBBQUAA4IBAQAdtOf5GEhd7fpawx3jt++GFclsE0kWDTGM
+MVzn2odkjq8SFqRaLZIaOz4hZaoXw5V+QBz9FGkGGM2sMexq8RaeiSY9WyGN6Oj5
+qz2qPMuZ8oZfiFMVBRflqNKFp05Jfdbdx4/OiL9lBeAUtTF37r0qhujop2ot2mUZ
+jGfibfZKhWaDtjJNn0IjF9dFQWp2BNStuY9u3MI+6VHyntjzf/tQKvCL/W8NIjYu
+zg5G8t6P2jt9HpOs/PQyKw+rAR+lQI/jJJkfXbKqDLnioeeSDJBLU30fKO5WPa8Y
+Z0nf1R7CqJgrTEeDgUwuRMLvyGPui3tbMfYmYb95HLCpTqnJUHvi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert74[] = {
+  0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x4c, 0xcd, 0x4a, 0x9a, 0x5b, 0x45, 0x13, 0x21, 0x8c,
+  0xcf, 0x90, 0x2f, 0x8b, 0x2b, 0x51, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+  0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+  0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+  0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+  0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+  0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+  0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+  0x30, 0x36, 0x30, 0x39, 0x31, 0x38, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+  0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+  0x38, 0x33, 0x38, 0x5a, 0x30, 0x71, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06,
+  0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65,
+  0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53,
+  0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03,
+  0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20,
+  0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x17,
+  0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x50, 0x6f, 0x73,
+  0x69, 0x74, 0x69, 0x76, 0x65, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x4f, 0x79, 0x58,
+  0x22, 0x93, 0xc9, 0x28, 0x3e, 0x52, 0x11, 0x00, 0x2f, 0xc0, 0xa9, 0x20,
+  0x8a, 0xd7, 0x2d, 0x55, 0x1e, 0x10, 0xe7, 0xb8, 0x7f, 0xe2, 0x86, 0x2a,
+  0xa4, 0xec, 0x5e, 0xe9, 0xe4, 0x92, 0x58, 0x96, 0x54, 0x0d, 0xf3, 0x17,
+  0xba, 0x41, 0x9e, 0x15, 0x91, 0x55, 0xef, 0xc2, 0xee, 0x00, 0x20, 0x18,
+  0x45, 0x83, 0x26, 0xdf, 0x20, 0xcc, 0x3d, 0xb3, 0xa1, 0x13, 0x31, 0x0a,
+  0x21, 0x5c, 0x79, 0x83, 0x79, 0xab, 0x24, 0x15, 0x5c, 0x56, 0xf0, 0xb4,
+  0x95, 0x98, 0xa1, 0xda, 0xd2, 0x1b, 0xea, 0x16, 0xb5, 0xcb, 0xb7, 0xb0,
+  0xc1, 0x53, 0xf9, 0xa4, 0x46, 0xda, 0xf0, 0x2e, 0x24, 0xb8, 0x62, 0x9e,
+  0x8e, 0x8b, 0x5e, 0x2b, 0x5a, 0x96, 0xa0, 0xea, 0x50, 0xe9, 0x88, 0xfb,
+  0x28, 0x2a, 0x4d, 0x9a, 0x9c, 0x48, 0x4f, 0x83, 0xb6, 0x87, 0xae, 0x4f,
+  0xc5, 0xc8, 0xb5, 0xd9, 0xfd, 0xbe, 0x3f, 0xd1, 0xa7, 0x9d, 0xc6, 0x2c,
+  0x13, 0x0d, 0xc0, 0x01, 0xc7, 0xb3, 0x70, 0xf3, 0x8f, 0x69, 0xbb, 0xb0,
+  0x3c, 0x10, 0xdf, 0xeb, 0x09, 0x00, 0x84, 0x3f, 0x6e, 0xef, 0xfc, 0xe3,
+  0x2d, 0xb4, 0xc7, 0x5d, 0x11, 0xcc, 0xf7, 0xf2, 0xf1, 0xf6, 0xe2, 0xe3,
+  0x00, 0x7e, 0x12, 0x0e, 0xde, 0x8d, 0x7d, 0x00, 0xca, 0x3a, 0x3d, 0xf6,
+  0x72, 0xe8, 0x79, 0x25, 0xa6, 0x08, 0x16, 0xf7, 0xab, 0x88, 0xff, 0x56,
+  0x5e, 0x09, 0x17, 0xc0, 0x5a, 0x82, 0x05, 0x62, 0x34, 0x2b, 0x28, 0x48,
+  0x32, 0x97, 0xd0, 0x84, 0x0c, 0xac, 0x13, 0x18, 0xdb, 0x9e, 0x66, 0xc7,
+  0xaa, 0x14, 0x8b, 0x11, 0x69, 0x4d, 0xf1, 0x09, 0xd3, 0xba, 0x5d, 0xa9,
+  0x88, 0x37, 0x62, 0xd8, 0xbf, 0x03, 0x9b, 0x9d, 0xd7, 0xe6, 0x05, 0x7e,
+  0xc2, 0x6a, 0x52, 0xaa, 0x8d, 0x07, 0x80, 0xea, 0x8e, 0x0e, 0xf3, 0x1d,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6e, 0x30, 0x82, 0x01,
+  0x6a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d,
+  0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb8, 0xca, 0x11,
+  0xe9, 0x06, 0x31, 0x79, 0xdb, 0xc3, 0x94, 0xc6, 0xe8, 0x19, 0x2a, 0xbc,
+  0xbb, 0x35, 0x16, 0x31, 0xa4, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+  0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73,
+  0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
+  0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d,
+  0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61,
+  0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81,
+  0x86, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x7a, 0x30, 0x78, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x02, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+  0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72,
+  0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e,
+  0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x1d,
+  0xb4, 0xe7, 0xf9, 0x18, 0x48, 0x5d, 0xed, 0xfa, 0x5a, 0xc3, 0x1d, 0xe3,
+  0xb7, 0xef, 0x86, 0x15, 0xc9, 0x6c, 0x13, 0x49, 0x16, 0x0d, 0x31, 0x8c,
+  0x31, 0x5c, 0xe7, 0xda, 0x87, 0x64, 0x8e, 0xaf, 0x12, 0x16, 0xa4, 0x5a,
+  0x2d, 0x92, 0x1a, 0x3b, 0x3e, 0x21, 0x65, 0xaa, 0x17, 0xc3, 0x95, 0x7e,
+  0x40, 0x1c, 0xfd, 0x14, 0x69, 0x06, 0x18, 0xcd, 0xac, 0x31, 0xec, 0x6a,
+  0xf1, 0x16, 0x9e, 0x89, 0x26, 0x3d, 0x5b, 0x21, 0x8d, 0xe8, 0xe8, 0xf9,
+  0xab, 0x3d, 0xaa, 0x3c, 0xcb, 0x99, 0xf2, 0x86, 0x5f, 0x88, 0x53, 0x15,
+  0x05, 0x17, 0xe5, 0xa8, 0xd2, 0x85, 0xa7, 0x4e, 0x49, 0x7d, 0xd6, 0xdd,
+  0xc7, 0x8f, 0xce, 0x88, 0xbf, 0x65, 0x05, 0xe0, 0x14, 0xb5, 0x31, 0x77,
+  0xee, 0xbd, 0x2a, 0x86, 0xe8, 0xe8, 0xa7, 0x6a, 0x2d, 0xda, 0x65, 0x19,
+  0x8c, 0x67, 0xe2, 0x6d, 0xf6, 0x4a, 0x85, 0x66, 0x83, 0xb6, 0x32, 0x4d,
+  0x9f, 0x42, 0x23, 0x17, 0xd7, 0x45, 0x41, 0x6a, 0x76, 0x04, 0xd4, 0xad,
+  0xb9, 0x8f, 0x6e, 0xdc, 0xc2, 0x3e, 0xe9, 0x51, 0xf2, 0x9e, 0xd8, 0xf3,
+  0x7f, 0xfb, 0x50, 0x2a, 0xf0, 0x8b, 0xfd, 0x6f, 0x0d, 0x22, 0x36, 0x2e,
+  0xce, 0x0e, 0x46, 0xf2, 0xde, 0x8f, 0xda, 0x3b, 0x7d, 0x1e, 0x93, 0xac,
+  0xfc, 0xf4, 0x32, 0x2b, 0x0f, 0xab, 0x01, 0x1f, 0xa5, 0x40, 0x8f, 0xe3,
+  0x24, 0x99, 0x1f, 0x5d, 0xb2, 0xaa, 0x0c, 0xb9, 0xe2, 0xa1, 0xe7, 0x92,
+  0x0c, 0x90, 0x4b, 0x53, 0x7d, 0x1f, 0x28, 0xee, 0x56, 0x3d, 0xaf, 0x18,
+  0x67, 0x49, 0xdf, 0xd5, 0x1e, 0xc2, 0xa8, 0x98, 0x2b, 0x4c, 0x47, 0x83,
+  0x81, 0x4c, 0x2e, 0x44, 0xc2, 0xef, 0xc8, 0x63, 0xee, 0x8b, 0x7b, 0x5b,
+  0x31, 0xf6, 0x26, 0x61, 0xbf, 0x79, 0x1c, 0xb0, 0xa9, 0x4e, 0xa9, 0xc9,
+  0x50, 0x7b, 0xe2,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            11:a3:b4:d0:ec:8d:b7:7f:9d:a0:cd:5d:2d:51:2f:42
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Validity
+            Not Before: May 24 00:00:00 2010 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Extended Validation Secure Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:cc:4a:96:33:cd:25:8d:67:ee:28:96:37:87:46:
+                    f0:f6:04:a2:84:7f:53:aa:96:e6:1f:b1:02:1c:6e:
+                    ed:7d:21:d4:d7:3c:1e:a2:d8:69:2f:a8:b7:f5:a2:
+                    ed:64:58:64:e1:44:65:36:49:41:20:01:8d:3b:13:
+                    e2:08:f3:0c:f2:57:39:93:37:b7:1c:93:44:83:8e:
+                    bf:2d:f1:a1:05:75:da:6e:ee:7b:6f:1b:ea:76:83:
+                    28:74:4a:1c:2b:d3:f5:c4:03:72:93:af:86:ce:09:
+                    8c:3c:75:d4:c9:0a:2f:72:f3:ad:bd:0e:30:3c:84:
+                    a1:73:1f:03:25:14:a5:8f:c3:d6:f4:b5:e4:dd:86:
+                    7a:f5:19:ba:68:f2:85:54:a2:30:11:ca:d1:92:cb:
+                    3b:74:06:12:a0:37:ab:6a:d8:54:11:df:6c:9a:16:
+                    94:b9:b4:a7:65:c6:74:2d:31:f3:4d:52:e9:55:51:
+                    9f:cb:3e:a2:8d:76:98:70:d2:6f:a6:65:45:2f:1b:
+                    85:bb:5b:6d:f9:f2:c0:04:66:13:84:7a:9d:ce:27:
+                    d8:f4:44:9e:bf:ac:be:99:db:6b:4f:db:58:21:b0:
+                    89:27:b4:8f:32:d6:4b:5e:72:91:5e:df:05:9d:d9:
+                    49:2f:f4:b6:6f:50:1f:75:cb:80:9d:e6:d3:e4:d1:
+                    f2:d3
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+            X509v3 Subject Key Identifier: 
+                88:44:51:FF:50:2A:69:5E:2D:88:F4:21:BA:D9:0C:F2:CE:CB:EA:7C
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://secure.comodo.com/CPS
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/COMODOAddTrustServerCA.crt
+                OCSP - URI:http://ocsp.comodoca.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         9a:43:bf:af:a4:72:5e:cd:7d:6f:7f:f4:fc:3d:8c:bb:70:e6:
+         1e:dd:04:fd:3f:dc:9d:9f:bf:89:76:9b:f2:86:31:fc:7f:b3:
+         ed:2a:91:53:2c:e2:aa:b0:e3:c8:2c:71:f7:15:8a:23:1c:f1:
+         69:2e:81:fb:b1:bc:62:0b:ab:1a:54:1c:d9:22:5e:34:4c:a5:
+         f6:23:0f:5d:7a:3d:db:43:cd:69:7e:17:37:52:cd:53:a1:c2:
+         11:d4:53:78:27:64:d5:89:41:4d:16:55:bb:90:cb:f0:d8:e4:
+         dd:dd:d3:09:64:48:28:ff:32:23:84:2f:8c:7b:55:2f:cf:29:
+         88:37:34:78:0f:33:aa:ff:b7:f2:96:a4:9b:44:80:b5:be:6c:
+         56:54:ab:a4:81:9e:25:18:28:54:3a:7f:2c:63:cf:59:20:8c:
+         18:6b:38:2c:b4:dd:ed:e3:40:de:0c:36:25:57:9a:c0:d1:60:
+         9e:5e:03:68:97:ae:1a:3b:ea:45:d7:51:99:49:ee:44:59:56:
+         0b:5e:b1:8f:68:ea:8a:9e:ca:d2:c9:a0:03:7e:70:25:f4:32:
+         c9:4e:50:83:87:a2:34:48:3d:4f:35:77:fc:d8:88:ea:f6:7d:
+         1e:ce:43:b6:d5:c2:6a:7e:38:66:63:4d:e7:ee:32:ef:0f:24:
+         e8:2a:67:fa
+-----BEGIN CERTIFICATE-----
+MIIFBjCCA+6gAwIBAgIQEaO00OyNt3+doM1dLVEvQjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMDA1MjQwMDAw
+MDBaFw0yMDA1MzAxMDQ4MzhaMIGOMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDE0MDIGA1UEAxMrQ09NT0RPIEV4dGVuZGVkIFZhbGlkYXRp
+b24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMxKljPNJY1n7iiWN4dG8PYEooR/U6qW5h+xAhxu7X0h1Nc8HqLYaS+ot/Wi
+7WRYZOFEZTZJQSABjTsT4gjzDPJXOZM3txyTRIOOvy3xoQV12m7ue28b6naDKHRK
+HCvT9cQDcpOvhs4JjDx11MkKL3Lzrb0OMDyEoXMfAyUUpY/D1vS15N2GevUZumjy
+hVSiMBHK0ZLLO3QGEqA3q2rYVBHfbJoWlLm0p2XGdC0x801S6VVRn8s+oo12mHDS
+b6ZlRS8bhbtbbfnywARmE4R6nc4n2PREnr+svpnba0/bWCGwiSe0jzLWS15ykV7f
+BZ3ZSS/0tm9QH3XLgJ3m0+TR8tMCAwEAAaOCAWkwggFlMB8GA1UdIwQYMBaAFAtY
+5YvGTBU3pECpMKkhvkc2Wlb/MB0GA1UdDgQWBBSIRFH/UCppXi2I9CG62Qzyzsvq
+fDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADA+BgNVHSAENzA1
+MDMGBFUdIAAwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNv
+bS9DUFMwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2NybC5jb21vZG9jYS5jb20v
+Q09NT0RPQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdAYIKwYBBQUHAQEEaDBm
+MD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9BZGRU
+cnVzdFNlcnZlckNBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2Rv
+Y2EuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQCaQ7+vpHJezX1vf/T8PYy7cOYe3QT9
+P9ydn7+JdpvyhjH8f7PtKpFTLOKqsOPILHH3FYojHPFpLoH7sbxiC6saVBzZIl40
+TKX2Iw9dej3bQ81pfhc3Us1TocIR1FN4J2TViUFNFlW7kMvw2OTd3dMJZEgo/zIj
+hC+Me1UvzymINzR4DzOq/7fylqSbRIC1vmxWVKukgZ4lGChUOn8sY89ZIIwYazgs
+tN3t40DeDDYlV5rA0WCeXgNol64aO+pF11GZSe5EWVYLXrGPaOqKnsrSyaADfnAl
+9DLJTlCDh6I0SD1PNXf82Ijq9n0ezkO21cJqfjhmY03n7jLvDyToKmf6
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert75[] = {
+  0x30, 0x82, 0x05, 0x06, 0x30, 0x82, 0x03, 0xee, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x11, 0xa3, 0xb4, 0xd0, 0xec, 0x8d, 0xb7, 0x7f, 0x9d,
+  0xa0, 0xcd, 0x5d, 0x2d, 0x51, 0x2f, 0x42, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+  0x17, 0x0d, 0x31, 0x30, 0x30, 0x35, 0x32, 0x34, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31,
+  0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, 0x81, 0x8e, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b,
+  0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65,
+  0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73,
+  0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30,
+  0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f,
+  0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65,
+  0x64, 0x31, 0x34, 0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e,
+  0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xcc, 0x4a, 0x96, 0x33, 0xcd, 0x25, 0x8d, 0x67,
+  0xee, 0x28, 0x96, 0x37, 0x87, 0x46, 0xf0, 0xf6, 0x04, 0xa2, 0x84, 0x7f,
+  0x53, 0xaa, 0x96, 0xe6, 0x1f, 0xb1, 0x02, 0x1c, 0x6e, 0xed, 0x7d, 0x21,
+  0xd4, 0xd7, 0x3c, 0x1e, 0xa2, 0xd8, 0x69, 0x2f, 0xa8, 0xb7, 0xf5, 0xa2,
+  0xed, 0x64, 0x58, 0x64, 0xe1, 0x44, 0x65, 0x36, 0x49, 0x41, 0x20, 0x01,
+  0x8d, 0x3b, 0x13, 0xe2, 0x08, 0xf3, 0x0c, 0xf2, 0x57, 0x39, 0x93, 0x37,
+  0xb7, 0x1c, 0x93, 0x44, 0x83, 0x8e, 0xbf, 0x2d, 0xf1, 0xa1, 0x05, 0x75,
+  0xda, 0x6e, 0xee, 0x7b, 0x6f, 0x1b, 0xea, 0x76, 0x83, 0x28, 0x74, 0x4a,
+  0x1c, 0x2b, 0xd3, 0xf5, 0xc4, 0x03, 0x72, 0x93, 0xaf, 0x86, 0xce, 0x09,
+  0x8c, 0x3c, 0x75, 0xd4, 0xc9, 0x0a, 0x2f, 0x72, 0xf3, 0xad, 0xbd, 0x0e,
+  0x30, 0x3c, 0x84, 0xa1, 0x73, 0x1f, 0x03, 0x25, 0x14, 0xa5, 0x8f, 0xc3,
+  0xd6, 0xf4, 0xb5, 0xe4, 0xdd, 0x86, 0x7a, 0xf5, 0x19, 0xba, 0x68, 0xf2,
+  0x85, 0x54, 0xa2, 0x30, 0x11, 0xca, 0xd1, 0x92, 0xcb, 0x3b, 0x74, 0x06,
+  0x12, 0xa0, 0x37, 0xab, 0x6a, 0xd8, 0x54, 0x11, 0xdf, 0x6c, 0x9a, 0x16,
+  0x94, 0xb9, 0xb4, 0xa7, 0x65, 0xc6, 0x74, 0x2d, 0x31, 0xf3, 0x4d, 0x52,
+  0xe9, 0x55, 0x51, 0x9f, 0xcb, 0x3e, 0xa2, 0x8d, 0x76, 0x98, 0x70, 0xd2,
+  0x6f, 0xa6, 0x65, 0x45, 0x2f, 0x1b, 0x85, 0xbb, 0x5b, 0x6d, 0xf9, 0xf2,
+  0xc0, 0x04, 0x66, 0x13, 0x84, 0x7a, 0x9d, 0xce, 0x27, 0xd8, 0xf4, 0x44,
+  0x9e, 0xbf, 0xac, 0xbe, 0x99, 0xdb, 0x6b, 0x4f, 0xdb, 0x58, 0x21, 0xb0,
+  0x89, 0x27, 0xb4, 0x8f, 0x32, 0xd6, 0x4b, 0x5e, 0x72, 0x91, 0x5e, 0xdf,
+  0x05, 0x9d, 0xd9, 0x49, 0x2f, 0xf4, 0xb6, 0x6f, 0x50, 0x1f, 0x75, 0xcb,
+  0x80, 0x9d, 0xe6, 0xd3, 0xe4, 0xd1, 0xf2, 0xd3, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x01, 0x69, 0x30, 0x82, 0x01, 0x65, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58,
+  0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21,
+  0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x88, 0x44, 0x51, 0xff, 0x50, 0x2a, 0x69,
+  0x5e, 0x2d, 0x88, 0xf4, 0x21, 0xba, 0xd9, 0x0c, 0xf2, 0xce, 0xcb, 0xea,
+  0x7c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x37, 0x30, 0x35,
+  0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2b, 0x30, 0x29,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x74, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x68, 0x30, 0x66,
+  0x30, 0x3e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02,
+  0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x41, 0x64, 0x64, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+  0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x9a, 0x43, 0xbf, 0xaf, 0xa4, 0x72, 0x5e, 0xcd, 0x7d, 0x6f,
+  0x7f, 0xf4, 0xfc, 0x3d, 0x8c, 0xbb, 0x70, 0xe6, 0x1e, 0xdd, 0x04, 0xfd,
+  0x3f, 0xdc, 0x9d, 0x9f, 0xbf, 0x89, 0x76, 0x9b, 0xf2, 0x86, 0x31, 0xfc,
+  0x7f, 0xb3, 0xed, 0x2a, 0x91, 0x53, 0x2c, 0xe2, 0xaa, 0xb0, 0xe3, 0xc8,
+  0x2c, 0x71, 0xf7, 0x15, 0x8a, 0x23, 0x1c, 0xf1, 0x69, 0x2e, 0x81, 0xfb,
+  0xb1, 0xbc, 0x62, 0x0b, 0xab, 0x1a, 0x54, 0x1c, 0xd9, 0x22, 0x5e, 0x34,
+  0x4c, 0xa5, 0xf6, 0x23, 0x0f, 0x5d, 0x7a, 0x3d, 0xdb, 0x43, 0xcd, 0x69,
+  0x7e, 0x17, 0x37, 0x52, 0xcd, 0x53, 0xa1, 0xc2, 0x11, 0xd4, 0x53, 0x78,
+  0x27, 0x64, 0xd5, 0x89, 0x41, 0x4d, 0x16, 0x55, 0xbb, 0x90, 0xcb, 0xf0,
+  0xd8, 0xe4, 0xdd, 0xdd, 0xd3, 0x09, 0x64, 0x48, 0x28, 0xff, 0x32, 0x23,
+  0x84, 0x2f, 0x8c, 0x7b, 0x55, 0x2f, 0xcf, 0x29, 0x88, 0x37, 0x34, 0x78,
+  0x0f, 0x33, 0xaa, 0xff, 0xb7, 0xf2, 0x96, 0xa4, 0x9b, 0x44, 0x80, 0xb5,
+  0xbe, 0x6c, 0x56, 0x54, 0xab, 0xa4, 0x81, 0x9e, 0x25, 0x18, 0x28, 0x54,
+  0x3a, 0x7f, 0x2c, 0x63, 0xcf, 0x59, 0x20, 0x8c, 0x18, 0x6b, 0x38, 0x2c,
+  0xb4, 0xdd, 0xed, 0xe3, 0x40, 0xde, 0x0c, 0x36, 0x25, 0x57, 0x9a, 0xc0,
+  0xd1, 0x60, 0x9e, 0x5e, 0x03, 0x68, 0x97, 0xae, 0x1a, 0x3b, 0xea, 0x45,
+  0xd7, 0x51, 0x99, 0x49, 0xee, 0x44, 0x59, 0x56, 0x0b, 0x5e, 0xb1, 0x8f,
+  0x68, 0xea, 0x8a, 0x9e, 0xca, 0xd2, 0xc9, 0xa0, 0x03, 0x7e, 0x70, 0x25,
+  0xf4, 0x32, 0xc9, 0x4e, 0x50, 0x83, 0x87, 0xa2, 0x34, 0x48, 0x3d, 0x4f,
+  0x35, 0x77, 0xfc, 0xd8, 0x88, 0xea, 0xf6, 0x7d, 0x1e, 0xce, 0x43, 0xb6,
+  0xd5, 0xc2, 0x6a, 0x7e, 0x38, 0x66, 0x63, 0x4d, 0xe7, 0xee, 0x32, 0xef,
+  0x0f, 0x24, 0xe8, 0x2a, 0x67, 0xfa,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 513 (0x201)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority
+        Validity
+            Not Before: Nov 16 01:15:40 2006 GMT
+            Not After : Nov 16 01:15:40 2026 GMT
+        Subject: C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., OU=http://certificates.starfieldtech.com/repository, CN=Starfield Secure Certification Authority/serialNumber=10688435
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e2:a7:5d:a3:ed:66:ef:6a:2f:2b:36:1f:dd:8d:
+                    d3:05:02:a0:ca:0f:5e:19:ae:38:72:cf:16:da:54:
+                    4a:cb:48:0a:f4:a1:73:11:65:85:43:c9:5b:17:0c:
+                    9a:2b:be:0f:98:51:7a:60:29:0d:6c:de:e2:e8:e5:
+                    15:4d:56:ff:90:d1:a7:a6:04:3f:60:07:4a:ca:6f:
+                    a5:10:e7:b3:f8:5c:b1:bc:2b:2a:dc:01:79:f5:1d:
+                    35:f5:7a:28:83:f2:93:73:82:89:ac:60:6d:cb:c2:
+                    48:c2:1d:d4:06:44:17:3c:ac:01:47:ab:3e:70:84:
+                    09:0b:b8:20:08:40:20:87:a1:63:1a:ca:3e:83:d2:
+                    37:b3:98:8d:32:3f:37:bf:a1:b7:5b:5f:de:5c:33:
+                    92:cf:3e:07:ce:b9:48:4b:e2:f0:55:50:2f:f8:70:
+                    42:89:d1:93:96:8a:63:d9:66:0d:e6:58:6e:b9:6d:
+                    90:bd:ca:dc:84:66:f2:39:8e:5b:a6:58:55:73:cb:
+                    62:6c:1b:d7:20:16:3b:2c:59:f5:cb:c8:56:32:4a:
+                    50:27:ba:55:d3:a8:01:cb:72:a9:74:8b:0c:ad:3a:
+                    e5:15:b6:2a:df:65:f8:de:8a:f5:ef:84:3b:f9:e7:
+                    54:65:0b:80:bd:47:45:a5:f0:44:d8:53:3b:be:80:
+                    f1:2f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                49:4B:52:27:D1:1B:BC:F2:A1:21:6A:62:7B:51:42:7A:8A:D7:D5:56
+            X509v3 Authority Key Identifier: 
+                keyid:BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.starfieldtech.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://certificates.starfieldtech.com/repository/sfroot.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://certificates.starfieldtech.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+    Signature Algorithm: sha1WithRSAEncryption
+         86:52:ba:b3:1f:a6:5e:6b:90:a6:64:2a:fc:45:b2:ae:9f:3e:
+         b3:62:af:db:1f:67:c4:bd:ca:a1:2f:c7:9c:0d:21:57:d0:f8:
+         36:21:ce:3a:25:3e:78:76:b3:d9:dd:bc:de:fb:6c:84:5f:0c:
+         a3:0d:12:eb:11:3b:71:5f:80:1e:f1:1f:6d:0e:5f:c1:ec:d4:
+         a5:f7:65:bb:1f:4c:95:01:13:b2:6a:9c:0b:eb:1f:9d:b1:e7:
+         ed:19:0d:bc:85:7c:f3:17:bd:59:63:ae:a7:1a:05:cd:47:e3:
+         2d:96:62:51:32:0a:08:68:4b:22:77:5f:f7:45:dc:61:de:f4:
+         cb:2b:22:29:44:25:d2:9f:0b:77:7a:a1:26:7c:4a:d7:0f:c2:
+         d1:3c:ba:0e:a7:95:9a:5b:05:0a:10:f9:55:5f:c1:97:8b:74:
+         cc:5e:28:69:13:7e:d0:0a:8d:9d:0f:60:54:7a:c4:8c:1b:35:
+         0f:74:7a:70:b2:82:cf:1d:b5:e2:8a:db:2a:c6:b2:51:69:bf:
+         12:17:92:60:17:aa:3d:5b:09:f8:87:65:1d:a7:a4:28:e5:22:
+         02:03:82:44:9a:34:63:9e:fb:28:cf:e8:cd:2e:0e:52:20:ed:
+         4a:cb:38:7c:9d:ae:6e:79:d7:95:2c:a8:91:f3:86:01:21:91:
+         4b:b5:40:a4
+-----BEGIN CERTIFICATE-----
+MIIFBzCCA++gAwIBAgICAgEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCVVMx
+JTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsT
+KVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2
+MTExNjAxMTU0MFoXDTI2MTExNjAxMTU0MFowgdwxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTkwNwYDVQQLEzBodHRwOi8vY2VydGlm
+aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKFN0
+YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUT
+CDEwNjg4NDM1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qddo+1m
+72ovKzYf3Y3TBQKgyg9eGa44cs8W2lRKy0gK9KFzEWWFQ8lbFwyaK74PmFF6YCkN
+bN7i6OUVTVb/kNGnpgQ/YAdKym+lEOez+FyxvCsq3AF59R019Xoog/KTc4KJrGBt
+y8JIwh3UBkQXPKwBR6s+cIQJC7ggCEAgh6FjGso+g9I3s5iNMj83v6G3W1/eXDOS
+zz4HzrlIS+LwVVAv+HBCidGTlopj2WYN5lhuuW2QvcrchGbyOY5bplhVc8tibBvX
+IBY7LFn1y8hWMkpQJ7pV06gBy3KpdIsMrTrlFbYq32X43or174Q7+edUZQuAvUdF
+pfBE2FM7voDxLwIDAQABo4IBRDCCAUAwHQYDVR0OBBYEFElLUifRG7zyoSFqYntR
+QnqK19VWMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtVrNzXEMIOqYjnMBIGA1UdEwEB
+/wQIMAYBAf8CAQAwOQYIKwYBBQUHAQEELTArMCkGCCsGAQUFBzABhh1odHRwOi8v
+b2NzcC5zdGFyZmllbGR0ZWNoLmNvbTBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8v
+Y2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvc2Zyb290
+LmNybDBRBgNVHSAESjBIMEYGBFUdIAAwPjA8BggrBgEFBQcCARYwaHR0cDovL2Nl
+cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB
+/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAhlK6sx+mXmuQpmQq/EWyrp8+s2Kv
+2x9nxL3KoS/HnA0hV9D4NiHOOiU+eHaz2d283vtshF8Mow0S6xE7cV+AHvEfbQ5f
+wezUpfdlux9MlQETsmqcC+sfnbHn7RkNvIV88xe9WWOupxoFzUfjLZZiUTIKCGhL
+Indf90XcYd70yysiKUQl0p8Ld3qhJnxK1w/C0Ty6DqeVmlsFChD5VV/Bl4t0zF4o
+aRN+0AqNnQ9gVHrEjBs1D3R6cLKCzx214orbKsayUWm/EheSYBeqPVsJ+IdlHaek
+KOUiAgOCRJo0Y577KM/ozS4OUiDtSss4fJ2ubnnXlSyokfOGASGRS7VApA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert76[] = {
+  0x30, 0x82, 0x05, 0x07, 0x30, 0x82, 0x03, 0xef, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x02, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x68, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74,
+  0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68,
+  0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43,
+  0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36,
+  0x31, 0x31, 0x31, 0x36, 0x30, 0x31, 0x31, 0x35, 0x34, 0x30, 0x5a, 0x17,
+  0x0d, 0x32, 0x36, 0x31, 0x31, 0x31, 0x36, 0x30, 0x31, 0x31, 0x35, 0x34,
+  0x30, 0x5a, 0x30, 0x81, 0xdc, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+  0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61,
+  0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53,
+  0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30,
+  0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72,
+  0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f,
+  0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66,
+  0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x31,
+  0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, 0x53, 0x74,
+  0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13,
+  0x08, 0x31, 0x30, 0x36, 0x38, 0x38, 0x34, 0x33, 0x35, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0xa7, 0x5d, 0xa3, 0xed, 0x66,
+  0xef, 0x6a, 0x2f, 0x2b, 0x36, 0x1f, 0xdd, 0x8d, 0xd3, 0x05, 0x02, 0xa0,
+  0xca, 0x0f, 0x5e, 0x19, 0xae, 0x38, 0x72, 0xcf, 0x16, 0xda, 0x54, 0x4a,
+  0xcb, 0x48, 0x0a, 0xf4, 0xa1, 0x73, 0x11, 0x65, 0x85, 0x43, 0xc9, 0x5b,
+  0x17, 0x0c, 0x9a, 0x2b, 0xbe, 0x0f, 0x98, 0x51, 0x7a, 0x60, 0x29, 0x0d,
+  0x6c, 0xde, 0xe2, 0xe8, 0xe5, 0x15, 0x4d, 0x56, 0xff, 0x90, 0xd1, 0xa7,
+  0xa6, 0x04, 0x3f, 0x60, 0x07, 0x4a, 0xca, 0x6f, 0xa5, 0x10, 0xe7, 0xb3,
+  0xf8, 0x5c, 0xb1, 0xbc, 0x2b, 0x2a, 0xdc, 0x01, 0x79, 0xf5, 0x1d, 0x35,
+  0xf5, 0x7a, 0x28, 0x83, 0xf2, 0x93, 0x73, 0x82, 0x89, 0xac, 0x60, 0x6d,
+  0xcb, 0xc2, 0x48, 0xc2, 0x1d, 0xd4, 0x06, 0x44, 0x17, 0x3c, 0xac, 0x01,
+  0x47, 0xab, 0x3e, 0x70, 0x84, 0x09, 0x0b, 0xb8, 0x20, 0x08, 0x40, 0x20,
+  0x87, 0xa1, 0x63, 0x1a, 0xca, 0x3e, 0x83, 0xd2, 0x37, 0xb3, 0x98, 0x8d,
+  0x32, 0x3f, 0x37, 0xbf, 0xa1, 0xb7, 0x5b, 0x5f, 0xde, 0x5c, 0x33, 0x92,
+  0xcf, 0x3e, 0x07, 0xce, 0xb9, 0x48, 0x4b, 0xe2, 0xf0, 0x55, 0x50, 0x2f,
+  0xf8, 0x70, 0x42, 0x89, 0xd1, 0x93, 0x96, 0x8a, 0x63, 0xd9, 0x66, 0x0d,
+  0xe6, 0x58, 0x6e, 0xb9, 0x6d, 0x90, 0xbd, 0xca, 0xdc, 0x84, 0x66, 0xf2,
+  0x39, 0x8e, 0x5b, 0xa6, 0x58, 0x55, 0x73, 0xcb, 0x62, 0x6c, 0x1b, 0xd7,
+  0x20, 0x16, 0x3b, 0x2c, 0x59, 0xf5, 0xcb, 0xc8, 0x56, 0x32, 0x4a, 0x50,
+  0x27, 0xba, 0x55, 0xd3, 0xa8, 0x01, 0xcb, 0x72, 0xa9, 0x74, 0x8b, 0x0c,
+  0xad, 0x3a, 0xe5, 0x15, 0xb6, 0x2a, 0xdf, 0x65, 0xf8, 0xde, 0x8a, 0xf5,
+  0xef, 0x84, 0x3b, 0xf9, 0xe7, 0x54, 0x65, 0x0b, 0x80, 0xbd, 0x47, 0x45,
+  0xa5, 0xf0, 0x44, 0xd8, 0x53, 0x3b, 0xbe, 0x80, 0xf1, 0x2f, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x44, 0x30, 0x82, 0x01, 0x40, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x49, 0x4b,
+  0x52, 0x27, 0xd1, 0x1b, 0xbc, 0xf2, 0xa1, 0x21, 0x6a, 0x62, 0x7b, 0x51,
+  0x42, 0x7a, 0x8a, 0xd7, 0xd5, 0x56, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xbf, 0x5f, 0xb7, 0xd1, 0xce,
+  0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, 0xdc, 0xd7, 0x10, 0xc2, 0x0e,
+  0xa9, 0x88, 0xe7, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+  0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+  0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x2d, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65,
+  0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4c,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0xa0,
+  0x3f, 0xa0, 0x3d, 0x86, 0x3b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73,
+  0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65,
+  0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+  0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x73, 0x66, 0x72, 0x6f, 0x6f, 0x74,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x51, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x4a, 0x30, 0x48, 0x30, 0x46, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30,
+  0x3e, 0x30, 0x3c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+  0x01, 0x16, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65,
+  0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+  0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+  0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x86, 0x52, 0xba, 0xb3, 0x1f, 0xa6, 0x5e, 0x6b, 0x90,
+  0xa6, 0x64, 0x2a, 0xfc, 0x45, 0xb2, 0xae, 0x9f, 0x3e, 0xb3, 0x62, 0xaf,
+  0xdb, 0x1f, 0x67, 0xc4, 0xbd, 0xca, 0xa1, 0x2f, 0xc7, 0x9c, 0x0d, 0x21,
+  0x57, 0xd0, 0xf8, 0x36, 0x21, 0xce, 0x3a, 0x25, 0x3e, 0x78, 0x76, 0xb3,
+  0xd9, 0xdd, 0xbc, 0xde, 0xfb, 0x6c, 0x84, 0x5f, 0x0c, 0xa3, 0x0d, 0x12,
+  0xeb, 0x11, 0x3b, 0x71, 0x5f, 0x80, 0x1e, 0xf1, 0x1f, 0x6d, 0x0e, 0x5f,
+  0xc1, 0xec, 0xd4, 0xa5, 0xf7, 0x65, 0xbb, 0x1f, 0x4c, 0x95, 0x01, 0x13,
+  0xb2, 0x6a, 0x9c, 0x0b, 0xeb, 0x1f, 0x9d, 0xb1, 0xe7, 0xed, 0x19, 0x0d,
+  0xbc, 0x85, 0x7c, 0xf3, 0x17, 0xbd, 0x59, 0x63, 0xae, 0xa7, 0x1a, 0x05,
+  0xcd, 0x47, 0xe3, 0x2d, 0x96, 0x62, 0x51, 0x32, 0x0a, 0x08, 0x68, 0x4b,
+  0x22, 0x77, 0x5f, 0xf7, 0x45, 0xdc, 0x61, 0xde, 0xf4, 0xcb, 0x2b, 0x22,
+  0x29, 0x44, 0x25, 0xd2, 0x9f, 0x0b, 0x77, 0x7a, 0xa1, 0x26, 0x7c, 0x4a,
+  0xd7, 0x0f, 0xc2, 0xd1, 0x3c, 0xba, 0x0e, 0xa7, 0x95, 0x9a, 0x5b, 0x05,
+  0x0a, 0x10, 0xf9, 0x55, 0x5f, 0xc1, 0x97, 0x8b, 0x74, 0xcc, 0x5e, 0x28,
+  0x69, 0x13, 0x7e, 0xd0, 0x0a, 0x8d, 0x9d, 0x0f, 0x60, 0x54, 0x7a, 0xc4,
+  0x8c, 0x1b, 0x35, 0x0f, 0x74, 0x7a, 0x70, 0xb2, 0x82, 0xcf, 0x1d, 0xb5,
+  0xe2, 0x8a, 0xdb, 0x2a, 0xc6, 0xb2, 0x51, 0x69, 0xbf, 0x12, 0x17, 0x92,
+  0x60, 0x17, 0xaa, 0x3d, 0x5b, 0x09, 0xf8, 0x87, 0x65, 0x1d, 0xa7, 0xa4,
+  0x28, 0xe5, 0x22, 0x02, 0x03, 0x82, 0x44, 0x9a, 0x34, 0x63, 0x9e, 0xfb,
+  0x28, 0xcf, 0xe8, 0xcd, 0x2e, 0x0e, 0x52, 0x20, 0xed, 0x4a, 0xcb, 0x38,
+  0x7c, 0x9d, 0xae, 0x6e, 0x79, 0xd7, 0x95, 0x2c, 0xa8, 0x91, 0xf3, 0x86,
+  0x01, 0x21, 0x91, 0x4b, 0xb5, 0x40, 0xa4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1276028635 (0x4c0ea6db)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+        Validity
+            Not Before: Oct  1 19:42:24 2006 GMT
+            Not After : Nov  4 03:38:44 2016 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+                    e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+                    a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+                    a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+                    cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+                    91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+                    f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+                    55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+                    be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+                    ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+                    22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+                    57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+                    68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+                    64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+                    39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+                    a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+                    87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+                    4b:cb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, E-mail Protection
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/2048ca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+
+            X509v3 Subject Key Identifier: 
+                B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+            X509v3 Authority Key Identifier: 
+                keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+            1.2.840.113533.7.65.0: 
+                0
+..V8.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         59:e1:94:14:89:c6:72:3c:e7:6b:75:4b:25:7a:2d:3e:a3:db:
+         ac:3c:72:4f:9b:30:b0:a2:5e:d6:62:5d:8f:36:6b:e7:dd:23:
+         59:c1:80:2c:a0:ed:7e:11:a0:c9:a3:bb:f6:96:b8:34:c9:fe:
+         c6:d7:58:b4:bb:27:7f:e5:6b:23:04:68:61:4b:16:57:df:e1:
+         7e:c0:c5:36:8f:0c:04:de:ef:77:68:68:83:6d:7c:05:fb:45:
+         dd:ce:16:56:91:39:d2:58:91:51:95:87:9e:4d:b4:0a:d7:05:
+         63:83:43:26:de:08:a6:19:77:9d:fe:59:a2:5f:db:32:33:4a:
+         65:10:c4:47:ef:ba:57:07:1f:4c:9f:af:68:65:ef:67:6d:9a:
+         de:1e:5e:4e:87:85:ee:9d:0d:7b:3d:d2:03:a9:dd:b7:05:04:
+         9e:95:0d:c1:b2:11:fd:5a:77:c4:1f:98:9f:2e:a0:d0:c9:7c:
+         d3:34:62:f5:2f:96:37:48:48:b4:21:fb:2f:ad:53:65:34:c2:
+         7b:4a:7c:fc:90:49:9f:f3:f7:37:08:9e:41:00:b2:63:1b:4b:
+         b9:f6:c1:7d:59:66:ab:d1:f3:8a:30:05:18:7a:41:47:ab:c7:
+         67:14:3a:7c:60:b1:08:4e:d0:ce:c7:e1:ad:a6:4d:ee:ae:32:
+         ac:ac:c6:5a
+-----BEGIN CERTIFICATE-----
+MIIFBzCCA++gAwIBAgIETA6m2zANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0wNjEwMDExOTQyMjRaFw0xNjEx
+MDQwMzM4NDRaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
+GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhp
+Z2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD1ZQ0Z6IKHLBfaaZAscS3
+so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80ltcZF+Y7arpl/DpIT4T2JR
+vvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46OFBbdzEbjbPHJEWap6xt
+ABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZdHFMsfpjNGgYWpGhz0DQE
+E1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdmt4i3ePLKCqg4qwpkwr9m
+XZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggFmMIIBYjAOBgNVHQ8BAf8E
+BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAnBgNVHSUEIDAeBggrBgEFBQcDAQYI
+KwYBBQUHAwIGCCsGAQUFBwMEMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYX
+aHR0cDovL29jc3AuZW50cnVzdC5uZXQwMgYDVR0fBCswKTAnoCWgI4YhaHR0cDov
+L2NybC5lbnRydXN0Lm5ldC8yMDQ4Y2EuY3JsME8GA1UdIARIMEYwRAYEVR0gADA8
+MDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJl
+cG9zaXRvcnkuaHRtMB0GA1UdDgQWBBSxPsNpA/i/RwHUmCYaCALvY2QrwzAfBgNV
+HSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDAZBgkqhkiG9n0HQQAEDDAKGwRW
+OC4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAWeGUFInGcjzna3VLJXotPqPbrDxy
+T5swsKJe1mJdjzZr590jWcGALKDtfhGgyaO79pa4NMn+xtdYtLsnf+VrIwRoYUsW
+V9/hfsDFNo8MBN7vd2hog218BftF3c4WVpE50liRUZWHnk20CtcFY4NDJt4Iphl3
+nf5Zol/bMjNKZRDER++6VwcfTJ+vaGXvZ22a3h5eToeF7p0Nez3SA6ndtwUEnpUN
+wbIR/Vp3xB+Yny6g0Ml80zRi9S+WN0hItCH7L61TZTTCe0p8/JBJn/P3NwieQQCy
+YxtLufbBfVlmq9HzijAFGHpBR6vHZxQ6fGCxCE7QzsfhraZN7q4yrKzGWg==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert77[] = {
+  0x30, 0x82, 0x05, 0x07, 0x30, 0x82, 0x03, 0xef, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x4c, 0x0e, 0xa6, 0xdb, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+  0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+  0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+  0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+  0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+  0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+  0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+  0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+  0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+  0x29, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x30, 0x30, 0x31, 0x31,
+  0x39, 0x34, 0x32, 0x32, 0x34, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31,
+  0x30, 0x34, 0x30, 0x33, 0x33, 0x38, 0x34, 0x34, 0x5a, 0x30, 0x6c, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44,
+  0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31,
+  0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77,
+  0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69,
+  0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65,
+  0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6, 0xcc, 0xe5, 0x73,
+  0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81,
+  0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34,
+  0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7,
+  0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, 0x3a, 0xd3, 0xdc, 0x94,
+  0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7,
+  0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e,
+  0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51,
+  0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, 0x06, 0xe5, 0x08, 0x91,
+  0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71,
+  0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b,
+  0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d,
+  0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, 0x51, 0x0a, 0x98, 0x41,
+  0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c,
+  0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c,
+  0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04,
+  0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, 0xdb, 0x5e, 0x64, 0xe1,
+  0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1,
+  0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7,
+  0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66,
+  0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, 0x5d, 0x1a, 0x50, 0x0b,
+  0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x66, 0x30, 0x82, 0x01,
+  0x62, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x01, 0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x20, 0x30, 0x1e,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e,
+  0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30,
+  0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27,
+  0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30, 0x34, 0x38, 0x63, 0x61, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x4f, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x48,
+  0x30, 0x46, 0x30, 0x44, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3c,
+  0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+  0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1,
+  0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a,
+  0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x1f, 0x06, 0x03, 0x55,
+  0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1,
+  0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9, 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24,
+  0x09, 0x16, 0xb9, 0x70, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56,
+  0x38, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x01, 0x00, 0x59, 0xe1, 0x94, 0x14, 0x89, 0xc6, 0x72, 0x3c, 0xe7,
+  0x6b, 0x75, 0x4b, 0x25, 0x7a, 0x2d, 0x3e, 0xa3, 0xdb, 0xac, 0x3c, 0x72,
+  0x4f, 0x9b, 0x30, 0xb0, 0xa2, 0x5e, 0xd6, 0x62, 0x5d, 0x8f, 0x36, 0x6b,
+  0xe7, 0xdd, 0x23, 0x59, 0xc1, 0x80, 0x2c, 0xa0, 0xed, 0x7e, 0x11, 0xa0,
+  0xc9, 0xa3, 0xbb, 0xf6, 0x96, 0xb8, 0x34, 0xc9, 0xfe, 0xc6, 0xd7, 0x58,
+  0xb4, 0xbb, 0x27, 0x7f, 0xe5, 0x6b, 0x23, 0x04, 0x68, 0x61, 0x4b, 0x16,
+  0x57, 0xdf, 0xe1, 0x7e, 0xc0, 0xc5, 0x36, 0x8f, 0x0c, 0x04, 0xde, 0xef,
+  0x77, 0x68, 0x68, 0x83, 0x6d, 0x7c, 0x05, 0xfb, 0x45, 0xdd, 0xce, 0x16,
+  0x56, 0x91, 0x39, 0xd2, 0x58, 0x91, 0x51, 0x95, 0x87, 0x9e, 0x4d, 0xb4,
+  0x0a, 0xd7, 0x05, 0x63, 0x83, 0x43, 0x26, 0xde, 0x08, 0xa6, 0x19, 0x77,
+  0x9d, 0xfe, 0x59, 0xa2, 0x5f, 0xdb, 0x32, 0x33, 0x4a, 0x65, 0x10, 0xc4,
+  0x47, 0xef, 0xba, 0x57, 0x07, 0x1f, 0x4c, 0x9f, 0xaf, 0x68, 0x65, 0xef,
+  0x67, 0x6d, 0x9a, 0xde, 0x1e, 0x5e, 0x4e, 0x87, 0x85, 0xee, 0x9d, 0x0d,
+  0x7b, 0x3d, 0xd2, 0x03, 0xa9, 0xdd, 0xb7, 0x05, 0x04, 0x9e, 0x95, 0x0d,
+  0xc1, 0xb2, 0x11, 0xfd, 0x5a, 0x77, 0xc4, 0x1f, 0x98, 0x9f, 0x2e, 0xa0,
+  0xd0, 0xc9, 0x7c, 0xd3, 0x34, 0x62, 0xf5, 0x2f, 0x96, 0x37, 0x48, 0x48,
+  0xb4, 0x21, 0xfb, 0x2f, 0xad, 0x53, 0x65, 0x34, 0xc2, 0x7b, 0x4a, 0x7c,
+  0xfc, 0x90, 0x49, 0x9f, 0xf3, 0xf7, 0x37, 0x08, 0x9e, 0x41, 0x00, 0xb2,
+  0x63, 0x1b, 0x4b, 0xb9, 0xf6, 0xc1, 0x7d, 0x59, 0x66, 0xab, 0xd1, 0xf3,
+  0x8a, 0x30, 0x05, 0x18, 0x7a, 0x41, 0x47, 0xab, 0xc7, 0x67, 0x14, 0x3a,
+  0x7c, 0x60, 0xb1, 0x08, 0x4e, 0xd0, 0xce, 0xc7, 0xe1, 0xad, 0xa6, 0x4d,
+  0xee, 0xae, 0x32, 0xac, 0xac, 0xc6, 0x5a,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1164679900 (0x456b9adc)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+        Validity
+            Not Before: Dec 10 20:55:43 2009 GMT
+            Not After : Dec 10 21:25:43 2019 GMT
+        Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1E
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:5b:04:54:77:dd:0e:24:66:dc:2a:a1:db:80:
+                    cc:5d:c7:5f:fd:52:16:58:da:5f:94:06:a9:b8:b6:
+                    b9:63:0c:47:20:82:ec:c7:95:4e:8b:b8:77:52:6a:
+                    3d:b5:87:a9:d6:e1:cc:74:e5:a6:c8:c0:d4:56:4f:
+                    8d:2e:d6:08:3e:0c:4c:43:3e:f0:41:93:5e:46:ef:
+                    39:e7:d9:65:2a:0c:76:50:27:bd:5b:0d:33:33:07:
+                    e0:f7:a2:a9:9c:e1:11:33:ad:66:fc:d2:2c:7a:aa:
+                    a3:73:16:be:93:85:75:0f:d7:37:8c:fa:23:b7:64:
+                    f8:e3:4c:6e:ed:b3:05:bd:e2:36:db:7c:de:76:44:
+                    da:82:72:76:b6:6e:ff:94:a1:d0:86:f7:10:cd:4a:
+                    5a:8b:b0:75:8c:66:52:80:4e:48:4c:49:83:a6:40:
+                    d7:77:81:13:4d:5e:72:7e:48:46:22:aa:0f:e2:3e:
+                    65:94:38:e1:72:71:fe:4a:71:09:ba:35:7f:55:89:
+                    3d:81:d5:b8:28:01:10:77:36:5a:10:85:d2:bd:60:
+                    84:2b:49:61:94:0c:de:4c:40:6a:2a:c4:79:60:84:
+                    24:82:32:69:4a:98:4b:e2:56:10:ba:03:45:51:20:
+                    d3:cf:da:8e:54:1b:45:b6:7a:ba:97:9a:5a:d8:c6:
+                    d1:5f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/rootca1.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/CPS
+
+            X509v3 Subject Key Identifier: 
+                5B:41:8A:B2:C4:43:C1:BD:BF:C8:54:41:55:9D:E0:96:AD:FF:B9:A1
+            X509v3 Authority Key Identifier: 
+                keyid:68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+
+            1.2.840.113533.7.65.0: 
+                0
+..V7.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         b2:3b:d2:9e:c1:bc:3b:48:b6:dc:d8:5a:18:66:53:c3:bd:35:
+         0d:48:42:2c:35:01:d8:10:a2:e2:e3:8d:2c:ba:a6:03:11:ed:
+         6b:b1:49:cb:5f:cd:ec:60:b3:ba:d4:02:eb:61:4f:4e:7e:f8:
+         df:90:5f:4e:d3:90:02:1c:52:da:12:00:2f:9b:71:da:04:12:
+         14:c1:90:83:2e:28:d2:10:40:11:8b:26:2d:eb:99:55:54:6f:
+         60:8e:c5:83:1d:c0:a3:3f:d5:8a:14:39:6a:1b:0d:ef:d3:5a:
+         77:39:cf:69:b4:bd:69:6f:4f:78:d3:a1:86:a3:9b:b7:d7:fb:
+         aa:2d:f0:fa:26:a1:f9:67:2c:88:4b:a5:34:d5:83:fb:4c:f1:
+         5b:70:22:66:1b:9b:59:4f:4d:ce:98:db:41:a4:fe:1a:a3:eb:
+         38:e6:f9:f1:39:02:9d:46:b6:c9:c2:9e:3e:82:b6:1f:9f:ca:
+         4a:a8:b1:06:5f:10:34:3b:fd:da:7b:ac:33:4e:ed:a6:b7:4b:
+         f3:91:f5:9c:0b:11:92:dc:13:6a:c8:d5:f1:3b:6d:96:6b:01:
+         e4:23:4c:b1:c1:e0:d2:12:21:9f:29:d4:ad:95:3d:a6:f7:e7:
+         32:c5:75:b7:0b:57:d8:a4:f9:c0:ec:ec:32:33:0c:4d:ae:e8:
+         08:d5:ec:aa
+-----BEGIN CERTIFICATE-----
+MIIFCjCCA/KgAwIBAgIERWua3DANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MTIxMDIwNTU0M1oXDTE5MTIxMDIx
+MjU0M1owgbExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvcnBhIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA5IEVudHJ1c3QsIEluYy4xLjAsBgNV
+BAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBMMUUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2WwRUd90OJGbcKqHbgMxdx1/9UhZY
+2l+UBqm4trljDEcgguzHlU6LuHdSaj21h6nW4cx05abIwNRWT40u1gg+DExDPvBB
+k15G7znn2WUqDHZQJ71bDTMzB+D3oqmc4REzrWb80ix6qqNzFr6ThXUP1zeM+iO3
+ZPjjTG7tswW94jbbfN52RNqCcna2bv+UodCG9xDNSlqLsHWMZlKATkhMSYOmQNd3
+gRNNXnJ+SEYiqg/iPmWUOOFycf5KcQm6NX9ViT2B1bgoARB3NloQhdK9YIQrSWGU
+DN5MQGoqxHlghCSCMmlKmEviVhC6A0VRINPP2o5UG0W2erqXmlrYxtFfAgMBAAGj
+ggEnMIIBIzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEF
+BQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0MDMG
+A1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvcm9vdGNhMS5j
+cmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
+ZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRbQYqyxEPBvb/IVEFVneCWrf+5oTAf
+BgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAZBgkqhkiG9n0HQQAEDDAK
+GwRWNy4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAsjvSnsG8O0i23NhaGGZTw701
+DUhCLDUB2BCi4uONLLqmAxHta7FJy1/N7GCzutQC62FPTn7435BfTtOQAhxS2hIA
+L5tx2gQSFMGQgy4o0hBAEYsmLeuZVVRvYI7Fgx3Aoz/VihQ5ahsN79NadznPabS9
+aW9PeNOhhqObt9f7qi3w+iah+WcsiEulNNWD+0zxW3AiZhubWU9NzpjbQaT+GqPr
+OOb58TkCnUa2ycKePoK2H5/KSqixBl8QNDv92nusM07tprdL85H1nAsRktwTasjV
+8TttlmsB5CNMscHg0hIhnynUrZU9pvfnMsV1twtX2KT5wOzsMjMMTa7oCNXsqg==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert78[] = {
+  0x30, 0x82, 0x05, 0x0a, 0x30, 0x82, 0x03, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x45, 0x6b, 0x9a, 0xdc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20,
+  0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64,
+  0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
+  0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16,
+  0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d,
+  0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65,
+  0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+  0x0d, 0x30, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32, 0x30, 0x35, 0x35, 0x34,
+  0x33, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32, 0x31,
+  0x32, 0x35, 0x34, 0x33, 0x5a, 0x30, 0x81, 0xb1, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30,
+  0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30,
+  0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e,
+  0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72,
+  0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32,
+  0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c,
+  0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+  0x2d, 0x20, 0x4c, 0x31, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xb6, 0x5b, 0x04, 0x54, 0x77, 0xdd, 0x0e, 0x24, 0x66, 0xdc,
+  0x2a, 0xa1, 0xdb, 0x80, 0xcc, 0x5d, 0xc7, 0x5f, 0xfd, 0x52, 0x16, 0x58,
+  0xda, 0x5f, 0x94, 0x06, 0xa9, 0xb8, 0xb6, 0xb9, 0x63, 0x0c, 0x47, 0x20,
+  0x82, 0xec, 0xc7, 0x95, 0x4e, 0x8b, 0xb8, 0x77, 0x52, 0x6a, 0x3d, 0xb5,
+  0x87, 0xa9, 0xd6, 0xe1, 0xcc, 0x74, 0xe5, 0xa6, 0xc8, 0xc0, 0xd4, 0x56,
+  0x4f, 0x8d, 0x2e, 0xd6, 0x08, 0x3e, 0x0c, 0x4c, 0x43, 0x3e, 0xf0, 0x41,
+  0x93, 0x5e, 0x46, 0xef, 0x39, 0xe7, 0xd9, 0x65, 0x2a, 0x0c, 0x76, 0x50,
+  0x27, 0xbd, 0x5b, 0x0d, 0x33, 0x33, 0x07, 0xe0, 0xf7, 0xa2, 0xa9, 0x9c,
+  0xe1, 0x11, 0x33, 0xad, 0x66, 0xfc, 0xd2, 0x2c, 0x7a, 0xaa, 0xa3, 0x73,
+  0x16, 0xbe, 0x93, 0x85, 0x75, 0x0f, 0xd7, 0x37, 0x8c, 0xfa, 0x23, 0xb7,
+  0x64, 0xf8, 0xe3, 0x4c, 0x6e, 0xed, 0xb3, 0x05, 0xbd, 0xe2, 0x36, 0xdb,
+  0x7c, 0xde, 0x76, 0x44, 0xda, 0x82, 0x72, 0x76, 0xb6, 0x6e, 0xff, 0x94,
+  0xa1, 0xd0, 0x86, 0xf7, 0x10, 0xcd, 0x4a, 0x5a, 0x8b, 0xb0, 0x75, 0x8c,
+  0x66, 0x52, 0x80, 0x4e, 0x48, 0x4c, 0x49, 0x83, 0xa6, 0x40, 0xd7, 0x77,
+  0x81, 0x13, 0x4d, 0x5e, 0x72, 0x7e, 0x48, 0x46, 0x22, 0xaa, 0x0f, 0xe2,
+  0x3e, 0x65, 0x94, 0x38, 0xe1, 0x72, 0x71, 0xfe, 0x4a, 0x71, 0x09, 0xba,
+  0x35, 0x7f, 0x55, 0x89, 0x3d, 0x81, 0xd5, 0xb8, 0x28, 0x01, 0x10, 0x77,
+  0x36, 0x5a, 0x10, 0x85, 0xd2, 0xbd, 0x60, 0x84, 0x2b, 0x49, 0x61, 0x94,
+  0x0c, 0xde, 0x4c, 0x40, 0x6a, 0x2a, 0xc4, 0x79, 0x60, 0x84, 0x24, 0x82,
+  0x32, 0x69, 0x4a, 0x98, 0x4b, 0xe2, 0x56, 0x10, 0xba, 0x03, 0x45, 0x51,
+  0x20, 0xd3, 0xcf, 0xda, 0x8e, 0x54, 0x1b, 0x45, 0xb6, 0x7a, 0xba, 0x97,
+  0x9a, 0x5a, 0xd8, 0xc6, 0xd1, 0x5f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x27, 0x30, 0x82, 0x01, 0x23, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+  0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e,
+  0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+  0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61, 0x31, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30,
+  0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30,
+  0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+  0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0x5b, 0x41, 0x8a, 0xb2, 0xc4, 0x43, 0xc1, 0xbd, 0xbf, 0xc8,
+  0x54, 0x41, 0x55, 0x9d, 0xe0, 0x96, 0xad, 0xff, 0xb9, 0xa1, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x68,
+  0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86, 0x66, 0xa4, 0xf1,
+  0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x19, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a,
+  0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xb2, 0x3b, 0xd2, 0x9e, 0xc1, 0xbc,
+  0x3b, 0x48, 0xb6, 0xdc, 0xd8, 0x5a, 0x18, 0x66, 0x53, 0xc3, 0xbd, 0x35,
+  0x0d, 0x48, 0x42, 0x2c, 0x35, 0x01, 0xd8, 0x10, 0xa2, 0xe2, 0xe3, 0x8d,
+  0x2c, 0xba, 0xa6, 0x03, 0x11, 0xed, 0x6b, 0xb1, 0x49, 0xcb, 0x5f, 0xcd,
+  0xec, 0x60, 0xb3, 0xba, 0xd4, 0x02, 0xeb, 0x61, 0x4f, 0x4e, 0x7e, 0xf8,
+  0xdf, 0x90, 0x5f, 0x4e, 0xd3, 0x90, 0x02, 0x1c, 0x52, 0xda, 0x12, 0x00,
+  0x2f, 0x9b, 0x71, 0xda, 0x04, 0x12, 0x14, 0xc1, 0x90, 0x83, 0x2e, 0x28,
+  0xd2, 0x10, 0x40, 0x11, 0x8b, 0x26, 0x2d, 0xeb, 0x99, 0x55, 0x54, 0x6f,
+  0x60, 0x8e, 0xc5, 0x83, 0x1d, 0xc0, 0xa3, 0x3f, 0xd5, 0x8a, 0x14, 0x39,
+  0x6a, 0x1b, 0x0d, 0xef, 0xd3, 0x5a, 0x77, 0x39, 0xcf, 0x69, 0xb4, 0xbd,
+  0x69, 0x6f, 0x4f, 0x78, 0xd3, 0xa1, 0x86, 0xa3, 0x9b, 0xb7, 0xd7, 0xfb,
+  0xaa, 0x2d, 0xf0, 0xfa, 0x26, 0xa1, 0xf9, 0x67, 0x2c, 0x88, 0x4b, 0xa5,
+  0x34, 0xd5, 0x83, 0xfb, 0x4c, 0xf1, 0x5b, 0x70, 0x22, 0x66, 0x1b, 0x9b,
+  0x59, 0x4f, 0x4d, 0xce, 0x98, 0xdb, 0x41, 0xa4, 0xfe, 0x1a, 0xa3, 0xeb,
+  0x38, 0xe6, 0xf9, 0xf1, 0x39, 0x02, 0x9d, 0x46, 0xb6, 0xc9, 0xc2, 0x9e,
+  0x3e, 0x82, 0xb6, 0x1f, 0x9f, 0xca, 0x4a, 0xa8, 0xb1, 0x06, 0x5f, 0x10,
+  0x34, 0x3b, 0xfd, 0xda, 0x7b, 0xac, 0x33, 0x4e, 0xed, 0xa6, 0xb7, 0x4b,
+  0xf3, 0x91, 0xf5, 0x9c, 0x0b, 0x11, 0x92, 0xdc, 0x13, 0x6a, 0xc8, 0xd5,
+  0xf1, 0x3b, 0x6d, 0x96, 0x6b, 0x01, 0xe4, 0x23, 0x4c, 0xb1, 0xc1, 0xe0,
+  0xd2, 0x12, 0x21, 0x9f, 0x29, 0xd4, 0xad, 0x95, 0x3d, 0xa6, 0xf7, 0xe7,
+  0x32, 0xc5, 0x75, 0xb7, 0x0b, 0x57, 0xd8, 0xa4, 0xf9, 0xc0, 0xec, 0xec,
+  0x32, 0x33, 0x0c, 0x4d, 0xae, 0xe8, 0x08, 0xd5, 0xec, 0xaa,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            7b:11:55:eb:78:9a:90:85:b5:8c:92:ff:42:b7:fe:56
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+        Validity
+            Not Before: Nov 17 00:00:00 2006 GMT
+            Not After : Nov 16 23:59:59 2016 GMT
+        Subject: C=US, O=thawte, Inc., OU=Terms of use at https://www.thawte.com/cps (c)06, CN=thawte Extended Validation SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b5:8d:47:f7:b0:48:76:9b:bd:fb:a9:cb:bf:04:
+                    31:a2:3d:9a:7e:30:29:d3:28:b8:fe:68:ce:cf:e9:
+                    30:6a:53:95:0e:50:65:80:26:c9:98:bf:f2:14:ff:
+                    06:7c:6a:7b:dc:50:07:e2:98:fa:df:cf:30:5d:ca:
+                    a8:b9:8a:9b:2d:2d:7e:59:8b:1a:f7:b3:c9:c3:69:
+                    80:0f:89:19:08:77:b2:52:55:ad:78:83:9d:6b:b9:
+                    87:e4:53:24:37:2c:fc:19:0e:8b:79:14:4d:be:80:
+                    9e:b4:9b:73:74:31:f2:38:ec:8a:af:2a:36:8e:64:
+                    ce:31:26:14:03:54:53:8e:fb:84:08:c1:7e:47:32:
+                    3d:71:e0:ba:ba:8c:82:58:96:4d:68:43:56:1a:f3:
+                    46:5a:32:99:95:b0:60:6f:e9:41:8a:48:cc:16:0d:
+                    44:68:b1:8a:dd:dd:17:3d:a4:9b:78:7f:2e:29:06:
+                    f0:dc:d5:d2:13:3f:c0:36:05:fd:c7:b5:b9:80:1b:
+                    8a:46:74:2f:f1:ab:79:9e:97:6e:f8:a5:13:5a:f3:
+                    fc:b5:d7:c8:96:19:37:ee:06:bc:c6:27:14:81:05:
+                    14:33:38:16:9f:4b:e2:0f:db:38:bb:f3:01:ef:35:
+                    2e:de:af:f1:e4:6f:6f:f7:96:00:56:5e:8f:60:94:
+                    1d:2f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://EVSecure-ocsp.thawte.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.thawte.com/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.thawte.com/ThawtePCA.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=PrivateLabel3-2048-234
+            X509v3 Subject Key Identifier: 
+                CD:32:E2:F2:5D:25:47:02:AA:8F:79:4B:32:EE:03:99:FD:30:49:D1
+            X509v3 Authority Key Identifier: 
+                keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+    Signature Algorithm: sha1WithRSAEncryption
+         0b:b4:96:ce:03:0c:d1:9d:af:cb:e3:39:56:0d:c6:22:a0:c9:
+         71:7d:ea:65:95:31:f1:dc:b6:1e:f2:8d:31:5d:61:b3:54:84:
+         13:cc:2b:3f:02:5c:c7:1f:15:01:82:90:1e:31:25:06:e3:32:
+         0c:87:f0:c3:be:9a:c4:00:41:f6:c6:91:e5:6c:3e:92:5d:a3:
+         e4:3d:1f:32:2d:31:1e:50:c1:02:21:b4:23:e3:07:75:9a:52:
+         45:51:fa:d3:1d:fd:01:6f:60:6d:25:d9:bf:43:b1:a7:43:6c:
+         ad:8c:bb:bc:f7:99:41:eb:d6:95:cf:20:5c:7e:6f:c4:2a:da:
+         4b:4d:1b:5b:c2:9f:b0:94:d4:bf:47:97:fd:9d:49:79:60:8e:
+         ae:96:19:a1:b0:eb:e8:df:42:c7:22:74:61:0c:25:a3:7f:8f:
+         45:d2:7e:e7:4a:6e:1d:4f:48:bb:c2:da:1a:7e:4a:59:81:fa:
+         1c:e3:fb:14:73:41:03:a1:77:fa:9b:06:fc:7c:33:bd:46:3d:
+         0c:06:17:85:7b:2a:7b:e3:36:e8:83:df:fa:aa:cb:32:0c:79:
+         aa:86:74:6c:44:54:f6:d8:07:9e:cd:98:f4:23:05:09:2f:a2:
+         53:b5:db:0a:81:cc:5f:23:cb:79:11:c5:11:5b:85:6b:27:01:
+         89:f3:0e:bb
+-----BEGIN CERTIFICATE-----
+MIIFCjCCA/KgAwIBAgIQexFV63iakIW1jJL/Qrf+VjANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMTYx
+MTE2MjM1OTU5WjCBizELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
+LjE5MDcGA1UECxMwVGVybXMgb2YgdXNlIGF0IGh0dHBzOi8vd3d3LnRoYXd0ZS5j
+b20vY3BzIChjKTA2MSowKAYDVQQDEyF0aGF3dGUgRXh0ZW5kZWQgVmFsaWRhdGlv
+biBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1jUf3sEh2
+m737qcu/BDGiPZp+MCnTKLj+aM7P6TBqU5UOUGWAJsmYv/IU/wZ8anvcUAfimPrf
+zzBdyqi5ipstLX5Zixr3s8nDaYAPiRkId7JSVa14g51ruYfkUyQ3LPwZDot5FE2+
+gJ60m3N0MfI47IqvKjaOZM4xJhQDVFOO+4QIwX5HMj1x4Lq6jIJYlk1oQ1Ya80Za
+MpmVsGBv6UGKSMwWDURosYrd3Rc9pJt4fy4pBvDc1dITP8A2Bf3HtbmAG4pGdC/x
+q3mel274pRNa8/y118iWGTfuBrzGJxSBBRQzOBafS+IP2zi78wHvNS7er/Hkb2/3
+lgBWXo9glB0vAgMBAAGjggFIMIIBRDA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUH
+MAGGH2h0dHA6Ly9FVlNlY3VyZS1vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgw
+BgEB/wIBADA7BgNVHSAENDAyMDAGBFUdIAAwKDAmBggrBgEFBQcCARYaaHR0cHM6
+Ly93d3cudGhhd3RlLmNvbS9jcHMwNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2Ny
+bC50aGF3dGUuY29tL1RoYXd0ZVBDQS5jcmwwDgYDVR0PAQH/BAQDAgEGMC4GA1Ud
+EQQnMCWkIzAhMR8wHQYDVQQDExZQcml2YXRlTGFiZWwzLTIwNDgtMjM0MB0GA1Ud
+DgQWBBTNMuLyXSVHAqqPeUsy7gOZ/TBJ0TAfBgNVHSMEGDAWgBR7W0XPr87Lev0x
+khpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAC7SWzgMM0Z2vy+M5Vg3GIqDJ
+cX3qZZUx8dy2HvKNMV1hs1SEE8wrPwJcxx8VAYKQHjElBuMyDIfww76axABB9saR
+5Ww+kl2j5D0fMi0xHlDBAiG0I+MHdZpSRVH60x39AW9gbSXZv0Oxp0NsrYy7vPeZ
+QevWlc8gXH5vxCraS00bW8KfsJTUv0eX/Z1JeWCOrpYZobDr6N9CxyJ0YQwlo3+P
+RdJ+50puHU9Iu8LaGn5KWYH6HOP7FHNBA6F3+psG/HwzvUY9DAYXhXsqe+M26IPf
++qrLMgx5qoZ0bERU9tgHns2Y9CMFCS+iU7XbCoHMXyPLeRHFEVuFaycBifMOuw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert79[] = {
+  0x30, 0x82, 0x05, 0x0a, 0x30, 0x82, 0x03, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x7b, 0x11, 0x55, 0xeb, 0x78, 0x9a, 0x90, 0x85, 0xb5,
+  0x8c, 0x92, 0xff, 0x42, 0xb7, 0xfe, 0x56, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+  0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+  0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+  0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+  0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x37,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31,
+  0x31, 0x31, 0x36, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81,
+  0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+  0x54, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x61, 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f,
+  0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36,
+  0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x74,
+  0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64,
+  0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xb5, 0x8d, 0x47, 0xf7, 0xb0, 0x48, 0x76,
+  0x9b, 0xbd, 0xfb, 0xa9, 0xcb, 0xbf, 0x04, 0x31, 0xa2, 0x3d, 0x9a, 0x7e,
+  0x30, 0x29, 0xd3, 0x28, 0xb8, 0xfe, 0x68, 0xce, 0xcf, 0xe9, 0x30, 0x6a,
+  0x53, 0x95, 0x0e, 0x50, 0x65, 0x80, 0x26, 0xc9, 0x98, 0xbf, 0xf2, 0x14,
+  0xff, 0x06, 0x7c, 0x6a, 0x7b, 0xdc, 0x50, 0x07, 0xe2, 0x98, 0xfa, 0xdf,
+  0xcf, 0x30, 0x5d, 0xca, 0xa8, 0xb9, 0x8a, 0x9b, 0x2d, 0x2d, 0x7e, 0x59,
+  0x8b, 0x1a, 0xf7, 0xb3, 0xc9, 0xc3, 0x69, 0x80, 0x0f, 0x89, 0x19, 0x08,
+  0x77, 0xb2, 0x52, 0x55, 0xad, 0x78, 0x83, 0x9d, 0x6b, 0xb9, 0x87, 0xe4,
+  0x53, 0x24, 0x37, 0x2c, 0xfc, 0x19, 0x0e, 0x8b, 0x79, 0x14, 0x4d, 0xbe,
+  0x80, 0x9e, 0xb4, 0x9b, 0x73, 0x74, 0x31, 0xf2, 0x38, 0xec, 0x8a, 0xaf,
+  0x2a, 0x36, 0x8e, 0x64, 0xce, 0x31, 0x26, 0x14, 0x03, 0x54, 0x53, 0x8e,
+  0xfb, 0x84, 0x08, 0xc1, 0x7e, 0x47, 0x32, 0x3d, 0x71, 0xe0, 0xba, 0xba,
+  0x8c, 0x82, 0x58, 0x96, 0x4d, 0x68, 0x43, 0x56, 0x1a, 0xf3, 0x46, 0x5a,
+  0x32, 0x99, 0x95, 0xb0, 0x60, 0x6f, 0xe9, 0x41, 0x8a, 0x48, 0xcc, 0x16,
+  0x0d, 0x44, 0x68, 0xb1, 0x8a, 0xdd, 0xdd, 0x17, 0x3d, 0xa4, 0x9b, 0x78,
+  0x7f, 0x2e, 0x29, 0x06, 0xf0, 0xdc, 0xd5, 0xd2, 0x13, 0x3f, 0xc0, 0x36,
+  0x05, 0xfd, 0xc7, 0xb5, 0xb9, 0x80, 0x1b, 0x8a, 0x46, 0x74, 0x2f, 0xf1,
+  0xab, 0x79, 0x9e, 0x97, 0x6e, 0xf8, 0xa5, 0x13, 0x5a, 0xf3, 0xfc, 0xb5,
+  0xd7, 0xc8, 0x96, 0x19, 0x37, 0xee, 0x06, 0xbc, 0xc6, 0x27, 0x14, 0x81,
+  0x05, 0x14, 0x33, 0x38, 0x16, 0x9f, 0x4b, 0xe2, 0x0f, 0xdb, 0x38, 0xbb,
+  0xf3, 0x01, 0xef, 0x35, 0x2e, 0xde, 0xaf, 0xf1, 0xe4, 0x6f, 0x6f, 0xf7,
+  0x96, 0x00, 0x56, 0x5e, 0x8f, 0x60, 0x94, 0x1d, 0x2f, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0x48, 0x30, 0x82, 0x01, 0x44, 0x30, 0x3b,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2f,
+  0x30, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x01, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45,
+  0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, 0x63, 0x73, 0x70,
+  0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3b, 0x06, 0x03, 0x55,
+  0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d,
+  0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x34, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0,
+  0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x1d,
+  0x11, 0x04, 0x27, 0x30, 0x25, 0xa4, 0x23, 0x30, 0x21, 0x31, 0x1f, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x50, 0x72, 0x69, 0x76,
+  0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x33, 0x2d, 0x32, 0x30,
+  0x34, 0x38, 0x2d, 0x32, 0x33, 0x34, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xcd, 0x32, 0xe2, 0xf2, 0x5d, 0x25, 0x47,
+  0x02, 0xaa, 0x8f, 0x79, 0x4b, 0x32, 0xee, 0x03, 0x99, 0xfd, 0x30, 0x49,
+  0xd1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31,
+  0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x0b, 0xb4, 0x96, 0xce, 0x03, 0x0c,
+  0xd1, 0x9d, 0xaf, 0xcb, 0xe3, 0x39, 0x56, 0x0d, 0xc6, 0x22, 0xa0, 0xc9,
+  0x71, 0x7d, 0xea, 0x65, 0x95, 0x31, 0xf1, 0xdc, 0xb6, 0x1e, 0xf2, 0x8d,
+  0x31, 0x5d, 0x61, 0xb3, 0x54, 0x84, 0x13, 0xcc, 0x2b, 0x3f, 0x02, 0x5c,
+  0xc7, 0x1f, 0x15, 0x01, 0x82, 0x90, 0x1e, 0x31, 0x25, 0x06, 0xe3, 0x32,
+  0x0c, 0x87, 0xf0, 0xc3, 0xbe, 0x9a, 0xc4, 0x00, 0x41, 0xf6, 0xc6, 0x91,
+  0xe5, 0x6c, 0x3e, 0x92, 0x5d, 0xa3, 0xe4, 0x3d, 0x1f, 0x32, 0x2d, 0x31,
+  0x1e, 0x50, 0xc1, 0x02, 0x21, 0xb4, 0x23, 0xe3, 0x07, 0x75, 0x9a, 0x52,
+  0x45, 0x51, 0xfa, 0xd3, 0x1d, 0xfd, 0x01, 0x6f, 0x60, 0x6d, 0x25, 0xd9,
+  0xbf, 0x43, 0xb1, 0xa7, 0x43, 0x6c, 0xad, 0x8c, 0xbb, 0xbc, 0xf7, 0x99,
+  0x41, 0xeb, 0xd6, 0x95, 0xcf, 0x20, 0x5c, 0x7e, 0x6f, 0xc4, 0x2a, 0xda,
+  0x4b, 0x4d, 0x1b, 0x5b, 0xc2, 0x9f, 0xb0, 0x94, 0xd4, 0xbf, 0x47, 0x97,
+  0xfd, 0x9d, 0x49, 0x79, 0x60, 0x8e, 0xae, 0x96, 0x19, 0xa1, 0xb0, 0xeb,
+  0xe8, 0xdf, 0x42, 0xc7, 0x22, 0x74, 0x61, 0x0c, 0x25, 0xa3, 0x7f, 0x8f,
+  0x45, 0xd2, 0x7e, 0xe7, 0x4a, 0x6e, 0x1d, 0x4f, 0x48, 0xbb, 0xc2, 0xda,
+  0x1a, 0x7e, 0x4a, 0x59, 0x81, 0xfa, 0x1c, 0xe3, 0xfb, 0x14, 0x73, 0x41,
+  0x03, 0xa1, 0x77, 0xfa, 0x9b, 0x06, 0xfc, 0x7c, 0x33, 0xbd, 0x46, 0x3d,
+  0x0c, 0x06, 0x17, 0x85, 0x7b, 0x2a, 0x7b, 0xe3, 0x36, 0xe8, 0x83, 0xdf,
+  0xfa, 0xaa, 0xcb, 0x32, 0x0c, 0x79, 0xaa, 0x86, 0x74, 0x6c, 0x44, 0x54,
+  0xf6, 0xd8, 0x07, 0x9e, 0xcd, 0x98, 0xf4, 0x23, 0x05, 0x09, 0x2f, 0xa2,
+  0x53, 0xb5, 0xdb, 0x0a, 0x81, 0xcc, 0x5f, 0x23, 0xcb, 0x79, 0x11, 0xc5,
+  0x11, 0x5b, 0x85, 0x6b, 0x27, 0x01, 0x89, 0xf3, 0x0e, 0xbb,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1276037400 (0x4c0ec918)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+        Validity
+            Not Before: Nov 11 14:57:22 2011 GMT
+            Not After : Nov 12 08:12:31 2021 GMT
+        Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1E
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:5b:04:54:77:dd:0e:24:66:dc:2a:a1:db:80:
+                    cc:5d:c7:5f:fd:52:16:58:da:5f:94:06:a9:b8:b6:
+                    b9:63:0c:47:20:82:ec:c7:95:4e:8b:b8:77:52:6a:
+                    3d:b5:87:a9:d6:e1:cc:74:e5:a6:c8:c0:d4:56:4f:
+                    8d:2e:d6:08:3e:0c:4c:43:3e:f0:41:93:5e:46:ef:
+                    39:e7:d9:65:2a:0c:76:50:27:bd:5b:0d:33:33:07:
+                    e0:f7:a2:a9:9c:e1:11:33:ad:66:fc:d2:2c:7a:aa:
+                    a3:73:16:be:93:85:75:0f:d7:37:8c:fa:23:b7:64:
+                    f8:e3:4c:6e:ed:b3:05:bd:e2:36:db:7c:de:76:44:
+                    da:82:72:76:b6:6e:ff:94:a1:d0:86:f7:10:cd:4a:
+                    5a:8b:b0:75:8c:66:52:80:4e:48:4c:49:83:a6:40:
+                    d7:77:81:13:4d:5e:72:7e:48:46:22:aa:0f:e2:3e:
+                    65:94:38:e1:72:71:fe:4a:71:09:ba:35:7f:55:89:
+                    3d:81:d5:b8:28:01:10:77:36:5a:10:85:d2:bd:60:
+                    84:2b:49:61:94:0c:de:4c:40:6a:2a:c4:79:60:84:
+                    24:82:32:69:4a:98:4b:e2:56:10:ba:03:45:51:20:
+                    d3:cf:da:8e:54:1b:45:b6:7a:ba:97:9a:5a:d8:c6:
+                    d1:5f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.entrust.net
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.entrust.net/rootca1.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://www.entrust.net/CPS
+
+            X509v3 Subject Key Identifier: 
+                5B:41:8A:B2:C4:43:C1:BD:BF:C8:54:41:55:9D:E0:96:AD:FF:B9:A1
+            X509v3 Authority Key Identifier: 
+                keyid:68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+
+            1.2.840.113533.7.65.0: 
+                0
+..V8.1....
+    Signature Algorithm: sha1WithRSAEncryption
+         a1:f1:a8:10:e8:e6:29:b9:22:6c:61:5b:2a:3f:3c:01:c7:82:
+         21:0b:e8:4e:0f:c4:c9:c6:bc:99:9d:f6:ef:5b:c7:69:b2:d9:
+         9e:ac:52:42:e9:8a:b8:31:c4:13:96:03:8f:65:93:06:69:fe:
+         28:b6:a6:fd:ad:87:8c:d5:cc:a6:e7:f9:1a:37:ef:32:2d:05:
+         2d:1e:4e:b9:d5:d5:d1:0f:9b:7f:24:4e:b8:90:ec:e6:69:bf:
+         9f:2a:3c:63:02:e1:69:a3:6e:a0:34:72:c8:50:50:b6:da:8e:
+         92:2e:b8:4b:28:fe:f4:92:f0:04:b6:d6:9d:3d:07:66:11:75:
+         6d:85:71:5e:32:f2:d7:0c:db:30:21:15:e1:74:b7:b5:eb:6b:
+         f9:73:ea:0a:49:ad:48:f6:23:23:8c:60:47:2c:51:96:b1:cc:
+         23:77:cd:96:c5:c6:cd:b5:4c:2c:95:f7:22:45:f8:b6:ad:84:
+         0c:08:ca:13:b0:a8:9d:35:6f:8b:48:d8:5f:b6:2b:a7:a8:27:
+         44:c3:0c:8e:a6:0d:e3:64:26:61:92:97:13:5e:80:31:0c:b7:
+         9e:90:20:87:0b:d0:aa:0a:06:04:27:3c:86:6a:20:0d:9d:bb:
+         ce:7d:57:c9:59:93:a2:03:3b:8c:b3:6f:42:fd:a4:d5:9b:ca:
+         01:aa:04:0c
+-----BEGIN CERTIFICATE-----
+MIIFDTCCA/WgAwIBAgIETA7JGDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTExMTExMTE0NTcyMloXDTIxMTExMjA4
+MTIzMVowgbExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvcnBhIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA5IEVudHJ1c3QsIEluYy4xLjAsBgNV
+BAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBMMUUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2WwRUd90OJGbcKqHbgMxdx1/9UhZY
+2l+UBqm4trljDEcgguzHlU6LuHdSaj21h6nW4cx05abIwNRWT40u1gg+DExDPvBB
+k15G7znn2WUqDHZQJ71bDTMzB+D3oqmc4REzrWb80ix6qqNzFr6ThXUP1zeM+iO3
+ZPjjTG7tswW94jbbfN52RNqCcna2bv+UodCG9xDNSlqLsHWMZlKATkhMSYOmQNd3
+gRNNXnJ+SEYiqg/iPmWUOOFycf5KcQm6NX9ViT2B1bgoARB3NloQhdK9YIQrSWGU
+DN5MQGoqxHlghCSCMmlKmEviVhC6A0VRINPP2o5UG0W2erqXmlrYxtFfAgMBAAGj
+ggEqMIIBJjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAzBggr
+BgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0
+MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvcm9vdGNh
+MS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93
+d3cuZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRbQYqyxEPBvb/IVEFVneCWrf+5
+oTAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAZBgkqhkiG9n0HQQAE
+DDAKGwRWOC4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAofGoEOjmKbkibGFbKj88
+AceCIQvoTg/Eyca8mZ3271vHabLZnqxSQumKuDHEE5YDj2WTBmn+KLam/a2HjNXM
+puf5GjfvMi0FLR5OudXV0Q+bfyROuJDs5mm/nyo8YwLhaaNuoDRyyFBQttqOki64
+Syj+9JLwBLbWnT0HZhF1bYVxXjLy1wzbMCEV4XS3tetr+XPqCkmtSPYjI4xgRyxR
+lrHMI3fNlsXGzbVMLJX3IkX4tq2EDAjKE7ConTVvi0jYX7Yrp6gnRMMMjqYN42Qm
+YZKXE16AMQy3npAghwvQqgoGBCc8hmogDZ27zn1XyVmTogM7jLNvQv2k1ZvKAaoE
+DA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert80[] = {
+  0x30, 0x82, 0x05, 0x0d, 0x30, 0x82, 0x03, 0xf5, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x4c, 0x0e, 0xc9, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+  0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20,
+  0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64,
+  0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
+  0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16,
+  0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d,
+  0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65,
+  0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x34, 0x35, 0x37, 0x32,
+  0x32, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31, 0x32, 0x30, 0x38,
+  0x31, 0x32, 0x33, 0x31, 0x5a, 0x30, 0x81, 0xb1, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30,
+  0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30,
+  0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e,
+  0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+  0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+  0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72,
+  0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32,
+  0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c,
+  0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+  0x2d, 0x20, 0x4c, 0x31, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xb6, 0x5b, 0x04, 0x54, 0x77, 0xdd, 0x0e, 0x24, 0x66, 0xdc,
+  0x2a, 0xa1, 0xdb, 0x80, 0xcc, 0x5d, 0xc7, 0x5f, 0xfd, 0x52, 0x16, 0x58,
+  0xda, 0x5f, 0x94, 0x06, 0xa9, 0xb8, 0xb6, 0xb9, 0x63, 0x0c, 0x47, 0x20,
+  0x82, 0xec, 0xc7, 0x95, 0x4e, 0x8b, 0xb8, 0x77, 0x52, 0x6a, 0x3d, 0xb5,
+  0x87, 0xa9, 0xd6, 0xe1, 0xcc, 0x74, 0xe5, 0xa6, 0xc8, 0xc0, 0xd4, 0x56,
+  0x4f, 0x8d, 0x2e, 0xd6, 0x08, 0x3e, 0x0c, 0x4c, 0x43, 0x3e, 0xf0, 0x41,
+  0x93, 0x5e, 0x46, 0xef, 0x39, 0xe7, 0xd9, 0x65, 0x2a, 0x0c, 0x76, 0x50,
+  0x27, 0xbd, 0x5b, 0x0d, 0x33, 0x33, 0x07, 0xe0, 0xf7, 0xa2, 0xa9, 0x9c,
+  0xe1, 0x11, 0x33, 0xad, 0x66, 0xfc, 0xd2, 0x2c, 0x7a, 0xaa, 0xa3, 0x73,
+  0x16, 0xbe, 0x93, 0x85, 0x75, 0x0f, 0xd7, 0x37, 0x8c, 0xfa, 0x23, 0xb7,
+  0x64, 0xf8, 0xe3, 0x4c, 0x6e, 0xed, 0xb3, 0x05, 0xbd, 0xe2, 0x36, 0xdb,
+  0x7c, 0xde, 0x76, 0x44, 0xda, 0x82, 0x72, 0x76, 0xb6, 0x6e, 0xff, 0x94,
+  0xa1, 0xd0, 0x86, 0xf7, 0x10, 0xcd, 0x4a, 0x5a, 0x8b, 0xb0, 0x75, 0x8c,
+  0x66, 0x52, 0x80, 0x4e, 0x48, 0x4c, 0x49, 0x83, 0xa6, 0x40, 0xd7, 0x77,
+  0x81, 0x13, 0x4d, 0x5e, 0x72, 0x7e, 0x48, 0x46, 0x22, 0xaa, 0x0f, 0xe2,
+  0x3e, 0x65, 0x94, 0x38, 0xe1, 0x72, 0x71, 0xfe, 0x4a, 0x71, 0x09, 0xba,
+  0x35, 0x7f, 0x55, 0x89, 0x3d, 0x81, 0xd5, 0xb8, 0x28, 0x01, 0x10, 0x77,
+  0x36, 0x5a, 0x10, 0x85, 0xd2, 0xbd, 0x60, 0x84, 0x2b, 0x49, 0x61, 0x94,
+  0x0c, 0xde, 0x4c, 0x40, 0x6a, 0x2a, 0xc4, 0x79, 0x60, 0x84, 0x24, 0x82,
+  0x32, 0x69, 0x4a, 0x98, 0x4b, 0xe2, 0x56, 0x10, 0xba, 0x03, 0x45, 0x51,
+  0x20, 0xd3, 0xcf, 0xda, 0x8e, 0x54, 0x1b, 0x45, 0xb6, 0x7a, 0xba, 0x97,
+  0x9a, 0x5a, 0xd8, 0xc6, 0xd1, 0x5f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x01, 0x2a, 0x30, 0x82, 0x01, 0x26, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+  0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x33, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30,
+  0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+  0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+  0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+  0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30,
+  0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61,
+  0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+  0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+  0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+  0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0x5b, 0x41, 0x8a, 0xb2, 0xc4, 0x43, 0xc1,
+  0xbd, 0xbf, 0xc8, 0x54, 0x41, 0x55, 0x9d, 0xe0, 0x96, 0xad, 0xff, 0xb9,
+  0xa1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0x68, 0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86,
+  0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x19,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04,
+  0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56, 0x38, 0x2e, 0x31, 0x03, 0x02, 0x00,
+  0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa1, 0xf1, 0xa8,
+  0x10, 0xe8, 0xe6, 0x29, 0xb9, 0x22, 0x6c, 0x61, 0x5b, 0x2a, 0x3f, 0x3c,
+  0x01, 0xc7, 0x82, 0x21, 0x0b, 0xe8, 0x4e, 0x0f, 0xc4, 0xc9, 0xc6, 0xbc,
+  0x99, 0x9d, 0xf6, 0xef, 0x5b, 0xc7, 0x69, 0xb2, 0xd9, 0x9e, 0xac, 0x52,
+  0x42, 0xe9, 0x8a, 0xb8, 0x31, 0xc4, 0x13, 0x96, 0x03, 0x8f, 0x65, 0x93,
+  0x06, 0x69, 0xfe, 0x28, 0xb6, 0xa6, 0xfd, 0xad, 0x87, 0x8c, 0xd5, 0xcc,
+  0xa6, 0xe7, 0xf9, 0x1a, 0x37, 0xef, 0x32, 0x2d, 0x05, 0x2d, 0x1e, 0x4e,
+  0xb9, 0xd5, 0xd5, 0xd1, 0x0f, 0x9b, 0x7f, 0x24, 0x4e, 0xb8, 0x90, 0xec,
+  0xe6, 0x69, 0xbf, 0x9f, 0x2a, 0x3c, 0x63, 0x02, 0xe1, 0x69, 0xa3, 0x6e,
+  0xa0, 0x34, 0x72, 0xc8, 0x50, 0x50, 0xb6, 0xda, 0x8e, 0x92, 0x2e, 0xb8,
+  0x4b, 0x28, 0xfe, 0xf4, 0x92, 0xf0, 0x04, 0xb6, 0xd6, 0x9d, 0x3d, 0x07,
+  0x66, 0x11, 0x75, 0x6d, 0x85, 0x71, 0x5e, 0x32, 0xf2, 0xd7, 0x0c, 0xdb,
+  0x30, 0x21, 0x15, 0xe1, 0x74, 0xb7, 0xb5, 0xeb, 0x6b, 0xf9, 0x73, 0xea,
+  0x0a, 0x49, 0xad, 0x48, 0xf6, 0x23, 0x23, 0x8c, 0x60, 0x47, 0x2c, 0x51,
+  0x96, 0xb1, 0xcc, 0x23, 0x77, 0xcd, 0x96, 0xc5, 0xc6, 0xcd, 0xb5, 0x4c,
+  0x2c, 0x95, 0xf7, 0x22, 0x45, 0xf8, 0xb6, 0xad, 0x84, 0x0c, 0x08, 0xca,
+  0x13, 0xb0, 0xa8, 0x9d, 0x35, 0x6f, 0x8b, 0x48, 0xd8, 0x5f, 0xb6, 0x2b,
+  0xa7, 0xa8, 0x27, 0x44, 0xc3, 0x0c, 0x8e, 0xa6, 0x0d, 0xe3, 0x64, 0x26,
+  0x61, 0x92, 0x97, 0x13, 0x5e, 0x80, 0x31, 0x0c, 0xb7, 0x9e, 0x90, 0x20,
+  0x87, 0x0b, 0xd0, 0xaa, 0x0a, 0x06, 0x04, 0x27, 0x3c, 0x86, 0x6a, 0x20,
+  0x0d, 0x9d, 0xbb, 0xce, 0x7d, 0x57, 0xc9, 0x59, 0x93, 0xa2, 0x03, 0x3b,
+  0x8c, 0xb3, 0x6f, 0x42, 0xfd, 0xa4, 0xd5, 0x9b, 0xca, 0x01, 0xaa, 0x04,
+  0x0c,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 268 (0x10c)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com
+        Validity
+            Not Before: Jun 29 17:39:16 2004 GMT
+            Not After : Jun 29 17:39:16 2024 GMT
+        Subject: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b7:32:c8:fe:e9:71:a6:04:85:ad:0c:11:64:df:
+                    ce:4d:ef:c8:03:18:87:3f:a1:ab:fb:3c:a6:9f:f0:
+                    c3:a1:da:d4:d8:6e:2b:53:90:fb:24:a4:3e:84:f0:
+                    9e:e8:5f:ec:e5:27:44:f5:28:a6:3f:7b:de:e0:2a:
+                    f0:c8:af:53:2f:9e:ca:05:01:93:1e:8f:66:1c:39:
+                    a7:4d:fa:5a:b6:73:04:25:66:eb:77:7f:e7:59:c6:
+                    4a:99:25:14:54:eb:26:c7:f3:7f:19:d5:30:70:8f:
+                    af:b0:46:2a:ff:ad:eb:29:ed:d7:9f:aa:04:87:a3:
+                    d4:f9:89:a5:34:5f:db:43:91:82:36:d9:66:3c:b1:
+                    b8:b9:82:fd:9c:3a:3e:10:c8:3b:ef:06:65:66:7a:
+                    9b:19:18:3d:ff:71:51:3c:30:2e:5f:be:3d:77:73:
+                    b2:5d:06:6c:c3:23:56:9a:2b:85:26:92:1c:a7:02:
+                    b3:e4:3f:0d:af:08:79:82:b8:36:3d:ea:9c:d3:35:
+                    b3:bc:69:ca:f5:cc:9d:e8:fd:64:8d:17:80:33:6e:
+                    5e:4a:5d:99:c9:1e:87:b4:9d:1a:c0:d5:6e:13:35:
+                    23:5e:df:9b:5f:3d:ef:d6:f7:76:c2:ea:3e:bb:78:
+                    0d:1c:42:67:6b:04:d8:f8:d6:da:6f:8b:f2:44:a0:
+                    01:ab
+                Exponent: 3 (0x3)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7
+            X509v3 Authority Key Identifier: 
+                DirName:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com
+                serial:01
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.starfieldtech.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://certificates.starfieldtech.com/repository/root.crl
+
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: http://certificates.starfieldtech.com/repository
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+    Signature Algorithm: sha1WithRSAEncryption
+         a5:62:f1:a7:c2:5d:25:a5:70:3d:bc:e2:2a:71:b3:7d:e4:0d:
+         37:1d:55:6a:6d:a1:b0:ab:98:00:e4:85:60:4a:20:cb:a1:f0:
+         3d:75:f7:94:da:43:7f:68:5c:25:08:b8:d2:7d:33:10:7b:dc:
+         76:67:f1:b8:e1:53:7c:e1:c6:83:5b:29:7b:8d:4a:6f:2e:7f:
+         44:a8:1a:45:6b:32:07:c7:78:ca:64:92:c2:b4:84:0c:dc:dd:
+         2d:5f:4d:bb:dd:8a:ea:38:dc:d9:66:a2:ec:41:ba:55:6d:5a:
+         64:3d:b7:03:cc:1c:e2:91:50:9e:e3:09:44:95:17:17:73:3d:
+         cc:25
+-----BEGIN CERTIFICATE-----
+MIIFEjCCBHugAwIBAgICAQwwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
+bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
+Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
+QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
+BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MzkxNloX
+DTI0MDYyOTE3MzkxNlowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVs
+ZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAy
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0A
+MIIBCAKCAQEAtzLI/ulxpgSFrQwRZN/OTe/IAxiHP6Gr+zymn/DDodrU2G4rU5D7
+JKQ+hPCe6F/s5SdE9SimP3ve4CrwyK9TL57KBQGTHo9mHDmnTfpatnMEJWbrd3/n
+WcZKmSUUVOsmx/N/GdUwcI+vsEYq/63rKe3Xn6oEh6PU+YmlNF/bQ5GCNtlmPLG4
+uYL9nDo+EMg77wZlZnqbGRg9/3FRPDAuX749d3OyXQZswyNWmiuFJpIcpwKz5D8N
+rwh5grg2Peqc0zWzvGnK9cyd6P1kjReAM25eSl2ZyR6HtJ0awNVuEzUjXt+bXz3v
+1vd2wuo+u3gNHEJnawTY+Nbab4vyRKABqwIBA6OCAfMwggHvMB0GA1UdDgQWBBS/
+X7fRzt0fhvRbVazc1xDCDqmI5zCB0gYDVR0jBIHKMIHHoYHBpIG+MIG7MSQwIgYD
+VQQHExtWYWxpQ2VydCBWYWxpZGF0aW9uIE5ldHdvcmsxFzAVBgNVBAoTDlZhbGlD
+ZXJ0LCBJbmMuMTUwMwYDVQQLEyxWYWxpQ2VydCBDbGFzcyAyIFBvbGljeSBWYWxp
+ZGF0aW9uIEF1dGhvcml0eTEhMB8GA1UEAxMYaHR0cDovL3d3dy52YWxpY2VydC5j
+b20vMSAwHgYJKoZIhvcNAQkBFhFpbmZvQHZhbGljZXJ0LmNvbYIBATAPBgNVHRMB
+Af8EBTADAQH/MDkGCCsGAQUFBwEBBC0wKzApBggrBgEFBQcwAYYdaHR0cDovL29j
+c3Auc3RhcmZpZWxkdGVjaC5jb20wSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2Nl
+cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5L3Jvb3QuY3Js
+MFEGA1UdIARKMEgwRgYEVR0gADA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY2VydGlm
+aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4GBAKVi8afCXSWlcD284ipxs33kDTcdVWptobCr
+mADkhWBKIMuh8D1195TaQ39oXCUIuNJ9MxB73HZn8bjhU3zhxoNbKXuNSm8uf0So
+GkVrMgfHeMpkksK0hAzc3S1fTbvdiuo43NlmouxBulVtWmQ9twPMHOKRUJ7jCUSV
+FxdzPcwl
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert81[] = {
+  0x30, 0x82, 0x05, 0x12, 0x30, 0x82, 0x04, 0x7b, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x02, 0x01, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xbb, 0x31,
+  0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61,
+  0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c,
+  0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+  0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63,
+  0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+  0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65,
+  0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34,
+  0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x33, 0x39, 0x31, 0x36, 0x5a, 0x17,
+  0x0d, 0x32, 0x34, 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x33, 0x39, 0x31,
+  0x36, 0x5a, 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+  0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+  0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c,
+  0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69,
+  0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66,
+  0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32,
+  0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+  0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, 0x00,
+  0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0x32, 0xc8,
+  0xfe, 0xe9, 0x71, 0xa6, 0x04, 0x85, 0xad, 0x0c, 0x11, 0x64, 0xdf, 0xce,
+  0x4d, 0xef, 0xc8, 0x03, 0x18, 0x87, 0x3f, 0xa1, 0xab, 0xfb, 0x3c, 0xa6,
+  0x9f, 0xf0, 0xc3, 0xa1, 0xda, 0xd4, 0xd8, 0x6e, 0x2b, 0x53, 0x90, 0xfb,
+  0x24, 0xa4, 0x3e, 0x84, 0xf0, 0x9e, 0xe8, 0x5f, 0xec, 0xe5, 0x27, 0x44,
+  0xf5, 0x28, 0xa6, 0x3f, 0x7b, 0xde, 0xe0, 0x2a, 0xf0, 0xc8, 0xaf, 0x53,
+  0x2f, 0x9e, 0xca, 0x05, 0x01, 0x93, 0x1e, 0x8f, 0x66, 0x1c, 0x39, 0xa7,
+  0x4d, 0xfa, 0x5a, 0xb6, 0x73, 0x04, 0x25, 0x66, 0xeb, 0x77, 0x7f, 0xe7,
+  0x59, 0xc6, 0x4a, 0x99, 0x25, 0x14, 0x54, 0xeb, 0x26, 0xc7, 0xf3, 0x7f,
+  0x19, 0xd5, 0x30, 0x70, 0x8f, 0xaf, 0xb0, 0x46, 0x2a, 0xff, 0xad, 0xeb,
+  0x29, 0xed, 0xd7, 0x9f, 0xaa, 0x04, 0x87, 0xa3, 0xd4, 0xf9, 0x89, 0xa5,
+  0x34, 0x5f, 0xdb, 0x43, 0x91, 0x82, 0x36, 0xd9, 0x66, 0x3c, 0xb1, 0xb8,
+  0xb9, 0x82, 0xfd, 0x9c, 0x3a, 0x3e, 0x10, 0xc8, 0x3b, 0xef, 0x06, 0x65,
+  0x66, 0x7a, 0x9b, 0x19, 0x18, 0x3d, 0xff, 0x71, 0x51, 0x3c, 0x30, 0x2e,
+  0x5f, 0xbe, 0x3d, 0x77, 0x73, 0xb2, 0x5d, 0x06, 0x6c, 0xc3, 0x23, 0x56,
+  0x9a, 0x2b, 0x85, 0x26, 0x92, 0x1c, 0xa7, 0x02, 0xb3, 0xe4, 0x3f, 0x0d,
+  0xaf, 0x08, 0x79, 0x82, 0xb8, 0x36, 0x3d, 0xea, 0x9c, 0xd3, 0x35, 0xb3,
+  0xbc, 0x69, 0xca, 0xf5, 0xcc, 0x9d, 0xe8, 0xfd, 0x64, 0x8d, 0x17, 0x80,
+  0x33, 0x6e, 0x5e, 0x4a, 0x5d, 0x99, 0xc9, 0x1e, 0x87, 0xb4, 0x9d, 0x1a,
+  0xc0, 0xd5, 0x6e, 0x13, 0x35, 0x23, 0x5e, 0xdf, 0x9b, 0x5f, 0x3d, 0xef,
+  0xd6, 0xf7, 0x76, 0xc2, 0xea, 0x3e, 0xbb, 0x78, 0x0d, 0x1c, 0x42, 0x67,
+  0x6b, 0x04, 0xd8, 0xf8, 0xd6, 0xda, 0x6f, 0x8b, 0xf2, 0x44, 0xa0, 0x01,
+  0xab, 0x02, 0x01, 0x03, 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xbf,
+  0x5f, 0xb7, 0xd1, 0xce, 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, 0xdc,
+  0xd7, 0x10, 0xc2, 0x0e, 0xa9, 0x88, 0xe7, 0x30, 0x81, 0xd2, 0x06, 0x03,
+  0x55, 0x1d, 0x23, 0x04, 0x81, 0xca, 0x30, 0x81, 0xc7, 0xa1, 0x81, 0xc1,
+  0xa4, 0x81, 0xbe, 0x30, 0x81, 0xbb, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03,
+  0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72,
+  0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15,
+  0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x43,
+  0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x35, 0x30,
+  0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2c, 0x56, 0x61, 0x6c, 0x69,
+  0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32,
+  0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69,
+  0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+  0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+  0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x69, 0x6e, 0x66, 0x6f,
+  0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x82, 0x01, 0x01, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x39, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2d, 0x30,
+  0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x01, 0x86, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+  0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64,
+  0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4a, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f, 0xa0, 0x3d, 0xa0,
+  0x3b, 0x86, 0x39, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65,
+  0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+  0x6f, 0x72, 0x79, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x51, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4a, 0x30, 0x48, 0x30,
+  0x46, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3e, 0x30, 0x3c, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x30, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66,
+  0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xa5, 0x62,
+  0xf1, 0xa7, 0xc2, 0x5d, 0x25, 0xa5, 0x70, 0x3d, 0xbc, 0xe2, 0x2a, 0x71,
+  0xb3, 0x7d, 0xe4, 0x0d, 0x37, 0x1d, 0x55, 0x6a, 0x6d, 0xa1, 0xb0, 0xab,
+  0x98, 0x00, 0xe4, 0x85, 0x60, 0x4a, 0x20, 0xcb, 0xa1, 0xf0, 0x3d, 0x75,
+  0xf7, 0x94, 0xda, 0x43, 0x7f, 0x68, 0x5c, 0x25, 0x08, 0xb8, 0xd2, 0x7d,
+  0x33, 0x10, 0x7b, 0xdc, 0x76, 0x67, 0xf1, 0xb8, 0xe1, 0x53, 0x7c, 0xe1,
+  0xc6, 0x83, 0x5b, 0x29, 0x7b, 0x8d, 0x4a, 0x6f, 0x2e, 0x7f, 0x44, 0xa8,
+  0x1a, 0x45, 0x6b, 0x32, 0x07, 0xc7, 0x78, 0xca, 0x64, 0x92, 0xc2, 0xb4,
+  0x84, 0x0c, 0xdc, 0xdd, 0x2d, 0x5f, 0x4d, 0xbb, 0xdd, 0x8a, 0xea, 0x38,
+  0xdc, 0xd9, 0x66, 0xa2, 0xec, 0x41, 0xba, 0x55, 0x6d, 0x5a, 0x64, 0x3d,
+  0xb7, 0x03, 0xcc, 0x1c, 0xe2, 0x91, 0x50, 0x9e, 0xe3, 0x09, 0x44, 0x95,
+  0x17, 0x17, 0x73, 0x3d, 0xcc, 0x25,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 120021506 (0x7276202)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+        Validity
+            Not Before: Apr 14 18:12:26 2010 GMT
+            Not After : Apr 14 18:12:14 2018 GMT
+        Subject: CN=Microsoft Internet Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (4096 bit)
+                Modulus:
+                    00:bd:f4:cd:27:a5:4a:d8:90:19:db:b2:0a:7b:60:
+                    a4:4e:8f:0e:98:a3:9c:7c:50:76:eb:b3:4a:8c:9f:
+                    18:b0:e7:9a:c5:2b:82:86:09:28:ac:11:12:32:26:
+                    f5:19:ea:f0:b8:67:68:c5:06:fd:f4:19:ae:d9:13:
+                    a0:d0:81:27:08:a6:79:9c:04:f5:48:32:2e:36:1f:
+                    ab:6b:26:ec:a1:43:b4:9d:80:b0:49:03:ea:82:49:
+                    5f:05:13:c5:a0:83:5f:e1:2a:f4:04:19:4b:7e:c8:
+                    da:88:bc:b5:5d:03:bc:78:56:a1:e9:7f:c5:6a:ef:
+                    b6:ff:1d:01:59:b7:1f:53:5a:5f:c6:f8:91:6d:c5:
+                    7d:43:93:18:74:45:ed:15:ba:b2:7c:c8:3a:34:14:
+                    1e:aa:63:f7:e5:d4:4b:c8:23:2b:87:69:95:13:99:
+                    09:14:ef:7a:01:20:4e:b7:c6:48:41:ae:c9:87:01:
+                    29:d9:c2:87:38:7f:b6:42:a4:f0:b2:ce:2d:fd:b4:
+                    4c:57:f0:a8:d6:cb:4e:fa:5f:5d:fd:b9:fb:09:dc:
+                    16:85:64:e5:71:9c:d5:f1:33:97:38:67:2e:9b:bc:
+                    17:36:05:7e:10:36:7f:7e:eb:98:5a:5b:1c:ad:a5:
+                    e7:09:10:7d:f9:4a:2f:b3:8f:37:15:d6:6f:b9:5b:
+                    37:dc:b7:9f:7f:8e:66:7f:23:5c:ed:12:7f:8c:07:
+                    f0:fe:19:f9:b8:34:43:7b:b2:ea:85:fb:8c:a9:aa:
+                    df:fd:91:0d:2c:f5:fb:af:97:89:f1:06:8a:af:49:
+                    f6:3c:2e:23:f6:44:16:25:91:11:e2:23:c3:ca:85:
+                    55:49:2a:c8:21:af:7d:11:26:86:b0:28:45:ba:87:
+                    ee:36:13:81:d5:4b:47:1a:8e:db:09:f1:d1:97:29:
+                    50:14:32:99:09:e3:f2:c0:e7:53:8f:6b:f4:fa:13:
+                    5c:3c:8d:ee:54:99:0f:27:47:4e:3c:12:f3:8f:12:
+                    17:46:f0:89:6a:45:b3:b5:3c:0c:77:45:04:2f:bd:
+                    be:b5:9e:98:3c:05:3b:bb:41:39:84:20:bc:79:04:
+                    d6:42:cd:3e:89:e9:e7:7a:37:49:10:b4:cc:9f:24:
+                    5c:23:a6:48:6e:fb:e3:d4:ee:21:29:93:e4:fd:80:
+                    1a:1b:3a:6c:c1:f7:eb:d9:d4:4d:be:f1:11:f6:a2:
+                    8e:42:24:a1:4f:69:b5:d2:68:14:89:d9:9f:90:d8:
+                    1f:9e:1b:e6:6d:64:25:29:b6:34:43:a4:5b:f5:0d:
+                    eb:74:06:7e:9f:f1:63:dc:45:a7:7c:3a:9a:5c:6b:
+                    73:d8:c3:58:04:8e:88:6f:13:d0:e6:d0:df:cd:c4:
+                    0a:0e:07
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.6334.1.0
+                  CPS: http://cybertrust.omniroot.com/repository.cfm
+                Policy: X509v3 Any Policy
+
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+                serial:01:A5
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+            X509v3 Subject Key Identifier: 
+                33:21:F0:CB:FE:A2:A0:44:92:DE:F6:3B:33:D8:5F:01:4B:97:78:5D
+    Signature Algorithm: sha1WithRSAEncryption
+         2b:48:f3:94:fb:44:c5:93:6a:d6:4d:fe:b4:13:4e:12:26:17:
+         ca:b2:5a:ab:09:b9:56:a4:6f:7f:57:9e:64:b2:f5:e4:d3:35:
+         ef:63:65:cb:e5:2c:15:9c:ef:ce:f8:2a:c5:92:64:2b:49:3e:
+         3c:36:6c:bd:18:9b:64:67:97:3f:ed:68:d0:16:c1:13:3c:f2:
+         51:a0:57:de:24:ce:35:ab:69:90:4e:2b:0c:3a:f9:b4:f1:80:
+         fa:6d:00:79:a6:3a:96:99:4e:3a:6e:54:d0:a3:59:6e:8b:1d:
+         95:49:bb:95:d8:75:b8:e1:12:33:ac:5c:27:bb:cb:55:71:d5:
+         fa:ed
+-----BEGIN CERTIFICATE-----
+MIIFEjCCBHugAwIBAgIEBydiAjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDQxNDE4MTIyNloXDTE4MDQxNDE4MTIxNFowJzElMCMG
+A1UEAxMcTWljcm9zb2Z0IEludGVybmV0IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAL30zSelStiQGduyCntgpE6PDpijnHxQduuzSoyf
+GLDnmsUrgoYJKKwREjIm9Rnq8LhnaMUG/fQZrtkToNCBJwimeZwE9UgyLjYfq2sm
+7KFDtJ2AsEkD6oJJXwUTxaCDX+Eq9AQZS37I2oi8tV0DvHhWoel/xWrvtv8dAVm3
+H1NaX8b4kW3FfUOTGHRF7RW6snzIOjQUHqpj9+XUS8gjK4dplROZCRTvegEgTrfG
+SEGuyYcBKdnChzh/tkKk8LLOLf20TFfwqNbLTvpfXf25+wncFoVk5XGc1fEzlzhn
+Lpu8FzYFfhA2f37rmFpbHK2l5wkQfflKL7OPNxXWb7lbN9y3n3+OZn8jXO0Sf4wH
+8P4Z+bg0Q3uy6oX7jKmq3/2RDSz1+6+XifEGiq9J9jwuI/ZEFiWREeIjw8qFVUkq
+yCGvfREmhrAoRbqH7jYTgdVLRxqO2wnx0ZcpUBQymQnj8sDnU49r9PoTXDyN7lSZ
+DydHTjwS848SF0bwiWpFs7U8DHdFBC+9vrWemDwFO7tBOYQgvHkE1kLNPonp53o3
+SRC0zJ8kXCOmSG7749TuISmT5P2AGhs6bMH369nUTb7xEfaijkIkoU9ptdJoFInZ
+n5DYH54b5m1kJSm2NEOkW/UN63QGfp/xY9xFp3w6mlxrc9jDWASOiG8T0ObQ383E
+Cg4HAgMBAAGjggF3MIIBczASBgNVHRMBAf8ECDAGAQH/AgEBMFsGA1UdIARUMFIw
+SAYJKwYBBAGxPgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9t
+bmlyb290LmNvbS9yZXBvc2l0b3J5LmNmbTAGBgRVHSAAMA4GA1UdDwEB/wQEAwIB
+hjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywg
+SW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUG
+A1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2kt
+YmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFDMh8Mv+oqBEkt72OzPYXwFL
+l3hdMA0GCSqGSIb3DQEBBQUAA4GBACtI85T7RMWTatZN/rQTThImF8qyWqsJuVak
+b39XnmSy9eTTNe9jZcvlLBWc7874KsWSZCtJPjw2bL0Ym2Rnlz/taNAWwRM88lGg
+V94kzjWraZBOKww6+bTxgPptAHmmOpaZTjpuVNCjWW6LHZVJu5XYdbjhEjOsXCe7
+y1Vx1frt
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert82[] = {
+  0x30, 0x82, 0x05, 0x12, 0x30, 0x82, 0x04, 0x7b, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x04, 0x07, 0x27, 0x62, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+  0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+  0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+  0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+  0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x30, 0x30, 0x34, 0x31, 0x34, 0x31, 0x38, 0x31, 0x32, 0x32,
+  0x36, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x34, 0x31, 0x34, 0x31, 0x38,
+  0x31, 0x32, 0x31, 0x34, 0x5a, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+  0x6f, 0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82,
+  0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82,
+  0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xbd, 0xf4, 0xcd, 0x27, 0xa5,
+  0x4a, 0xd8, 0x90, 0x19, 0xdb, 0xb2, 0x0a, 0x7b, 0x60, 0xa4, 0x4e, 0x8f,
+  0x0e, 0x98, 0xa3, 0x9c, 0x7c, 0x50, 0x76, 0xeb, 0xb3, 0x4a, 0x8c, 0x9f,
+  0x18, 0xb0, 0xe7, 0x9a, 0xc5, 0x2b, 0x82, 0x86, 0x09, 0x28, 0xac, 0x11,
+  0x12, 0x32, 0x26, 0xf5, 0x19, 0xea, 0xf0, 0xb8, 0x67, 0x68, 0xc5, 0x06,
+  0xfd, 0xf4, 0x19, 0xae, 0xd9, 0x13, 0xa0, 0xd0, 0x81, 0x27, 0x08, 0xa6,
+  0x79, 0x9c, 0x04, 0xf5, 0x48, 0x32, 0x2e, 0x36, 0x1f, 0xab, 0x6b, 0x26,
+  0xec, 0xa1, 0x43, 0xb4, 0x9d, 0x80, 0xb0, 0x49, 0x03, 0xea, 0x82, 0x49,
+  0x5f, 0x05, 0x13, 0xc5, 0xa0, 0x83, 0x5f, 0xe1, 0x2a, 0xf4, 0x04, 0x19,
+  0x4b, 0x7e, 0xc8, 0xda, 0x88, 0xbc, 0xb5, 0x5d, 0x03, 0xbc, 0x78, 0x56,
+  0xa1, 0xe9, 0x7f, 0xc5, 0x6a, 0xef, 0xb6, 0xff, 0x1d, 0x01, 0x59, 0xb7,
+  0x1f, 0x53, 0x5a, 0x5f, 0xc6, 0xf8, 0x91, 0x6d, 0xc5, 0x7d, 0x43, 0x93,
+  0x18, 0x74, 0x45, 0xed, 0x15, 0xba, 0xb2, 0x7c, 0xc8, 0x3a, 0x34, 0x14,
+  0x1e, 0xaa, 0x63, 0xf7, 0xe5, 0xd4, 0x4b, 0xc8, 0x23, 0x2b, 0x87, 0x69,
+  0x95, 0x13, 0x99, 0x09, 0x14, 0xef, 0x7a, 0x01, 0x20, 0x4e, 0xb7, 0xc6,
+  0x48, 0x41, 0xae, 0xc9, 0x87, 0x01, 0x29, 0xd9, 0xc2, 0x87, 0x38, 0x7f,
+  0xb6, 0x42, 0xa4, 0xf0, 0xb2, 0xce, 0x2d, 0xfd, 0xb4, 0x4c, 0x57, 0xf0,
+  0xa8, 0xd6, 0xcb, 0x4e, 0xfa, 0x5f, 0x5d, 0xfd, 0xb9, 0xfb, 0x09, 0xdc,
+  0x16, 0x85, 0x64, 0xe5, 0x71, 0x9c, 0xd5, 0xf1, 0x33, 0x97, 0x38, 0x67,
+  0x2e, 0x9b, 0xbc, 0x17, 0x36, 0x05, 0x7e, 0x10, 0x36, 0x7f, 0x7e, 0xeb,
+  0x98, 0x5a, 0x5b, 0x1c, 0xad, 0xa5, 0xe7, 0x09, 0x10, 0x7d, 0xf9, 0x4a,
+  0x2f, 0xb3, 0x8f, 0x37, 0x15, 0xd6, 0x6f, 0xb9, 0x5b, 0x37, 0xdc, 0xb7,
+  0x9f, 0x7f, 0x8e, 0x66, 0x7f, 0x23, 0x5c, 0xed, 0x12, 0x7f, 0x8c, 0x07,
+  0xf0, 0xfe, 0x19, 0xf9, 0xb8, 0x34, 0x43, 0x7b, 0xb2, 0xea, 0x85, 0xfb,
+  0x8c, 0xa9, 0xaa, 0xdf, 0xfd, 0x91, 0x0d, 0x2c, 0xf5, 0xfb, 0xaf, 0x97,
+  0x89, 0xf1, 0x06, 0x8a, 0xaf, 0x49, 0xf6, 0x3c, 0x2e, 0x23, 0xf6, 0x44,
+  0x16, 0x25, 0x91, 0x11, 0xe2, 0x23, 0xc3, 0xca, 0x85, 0x55, 0x49, 0x2a,
+  0xc8, 0x21, 0xaf, 0x7d, 0x11, 0x26, 0x86, 0xb0, 0x28, 0x45, 0xba, 0x87,
+  0xee, 0x36, 0x13, 0x81, 0xd5, 0x4b, 0x47, 0x1a, 0x8e, 0xdb, 0x09, 0xf1,
+  0xd1, 0x97, 0x29, 0x50, 0x14, 0x32, 0x99, 0x09, 0xe3, 0xf2, 0xc0, 0xe7,
+  0x53, 0x8f, 0x6b, 0xf4, 0xfa, 0x13, 0x5c, 0x3c, 0x8d, 0xee, 0x54, 0x99,
+  0x0f, 0x27, 0x47, 0x4e, 0x3c, 0x12, 0xf3, 0x8f, 0x12, 0x17, 0x46, 0xf0,
+  0x89, 0x6a, 0x45, 0xb3, 0xb5, 0x3c, 0x0c, 0x77, 0x45, 0x04, 0x2f, 0xbd,
+  0xbe, 0xb5, 0x9e, 0x98, 0x3c, 0x05, 0x3b, 0xbb, 0x41, 0x39, 0x84, 0x20,
+  0xbc, 0x79, 0x04, 0xd6, 0x42, 0xcd, 0x3e, 0x89, 0xe9, 0xe7, 0x7a, 0x37,
+  0x49, 0x10, 0xb4, 0xcc, 0x9f, 0x24, 0x5c, 0x23, 0xa6, 0x48, 0x6e, 0xfb,
+  0xe3, 0xd4, 0xee, 0x21, 0x29, 0x93, 0xe4, 0xfd, 0x80, 0x1a, 0x1b, 0x3a,
+  0x6c, 0xc1, 0xf7, 0xeb, 0xd9, 0xd4, 0x4d, 0xbe, 0xf1, 0x11, 0xf6, 0xa2,
+  0x8e, 0x42, 0x24, 0xa1, 0x4f, 0x69, 0xb5, 0xd2, 0x68, 0x14, 0x89, 0xd9,
+  0x9f, 0x90, 0xd8, 0x1f, 0x9e, 0x1b, 0xe6, 0x6d, 0x64, 0x25, 0x29, 0xb6,
+  0x34, 0x43, 0xa4, 0x5b, 0xf5, 0x0d, 0xeb, 0x74, 0x06, 0x7e, 0x9f, 0xf1,
+  0x63, 0xdc, 0x45, 0xa7, 0x7c, 0x3a, 0x9a, 0x5c, 0x6b, 0x73, 0xd8, 0xc3,
+  0x58, 0x04, 0x8e, 0x88, 0x6f, 0x13, 0xd0, 0xe6, 0xd0, 0xdf, 0xcd, 0xc4,
+  0x0a, 0x0e, 0x07, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x77,
+  0x30, 0x82, 0x01, 0x73, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+  0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+  0x30, 0x5b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x54, 0x30, 0x52, 0x30,
+  0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+  0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d,
+  0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+  0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66,
+  0x6d, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x86, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81,
+  0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09,
+  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30,
+  0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20,
+  0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31,
+  0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54,
+  0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74,
+  0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38,
+  0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+  0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d,
+  0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38,
+  0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x33, 0x21, 0xf0, 0xcb, 0xfe,
+  0xa2, 0xa0, 0x44, 0x92, 0xde, 0xf6, 0x3b, 0x33, 0xd8, 0x5f, 0x01, 0x4b,
+  0x97, 0x78, 0x5d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x2b, 0x48,
+  0xf3, 0x94, 0xfb, 0x44, 0xc5, 0x93, 0x6a, 0xd6, 0x4d, 0xfe, 0xb4, 0x13,
+  0x4e, 0x12, 0x26, 0x17, 0xca, 0xb2, 0x5a, 0xab, 0x09, 0xb9, 0x56, 0xa4,
+  0x6f, 0x7f, 0x57, 0x9e, 0x64, 0xb2, 0xf5, 0xe4, 0xd3, 0x35, 0xef, 0x63,
+  0x65, 0xcb, 0xe5, 0x2c, 0x15, 0x9c, 0xef, 0xce, 0xf8, 0x2a, 0xc5, 0x92,
+  0x64, 0x2b, 0x49, 0x3e, 0x3c, 0x36, 0x6c, 0xbd, 0x18, 0x9b, 0x64, 0x67,
+  0x97, 0x3f, 0xed, 0x68, 0xd0, 0x16, 0xc1, 0x13, 0x3c, 0xf2, 0x51, 0xa0,
+  0x57, 0xde, 0x24, 0xce, 0x35, 0xab, 0x69, 0x90, 0x4e, 0x2b, 0x0c, 0x3a,
+  0xf9, 0xb4, 0xf1, 0x80, 0xfa, 0x6d, 0x00, 0x79, 0xa6, 0x3a, 0x96, 0x99,
+  0x4e, 0x3a, 0x6e, 0x54, 0xd0, 0xa3, 0x59, 0x6e, 0x8b, 0x1d, 0x95, 0x49,
+  0xbb, 0x95, 0xd8, 0x75, 0xb8, 0xe1, 0x12, 0x33, 0xac, 0x5c, 0x27, 0xbb,
+  0xcb, 0x55, 0x71, 0xd5, 0xfa, 0xed,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            57:bf:fb:03:fb:2c:46:d4:e1:9e:ce:e0:d7:43:7f:13
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2021 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+                    4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+                    08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+                    2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+                    8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+                    a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+                    54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+                    d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+                    7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+                    bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+                    f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+                    ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+                    f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+                    21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+                    63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+                    ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+                    9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+                    25:15
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 Subject Key Identifier: 
+                7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+            X509v3 Extended Key Usage: 
+                Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
+                serial:70:BA:E4:1D:10:D9:29:34:B6:38:CA:7B:03:CC:BA:BF
+
+    Signature Algorithm: sha1WithRSAEncryption
+         a9:7b:66:29:30:f7:d5:b4:a6:96:12:d0:ee:72:f0:58:11:69:
+         15:55:5f:41:ff:d2:12:84:13:a4:d9:03:66:ff:a9:e0:4c:c9:
+         ed:8c:72:8b:b4:d7:55:3b:29:15:60:c8:3c:21:ef:44:2e:93:
+         3d:c6:0b:0c:8d:24:3f:1e:fb:01:5a:7a:dd:83:66:14:d1:c7:
+         fd:30:53:48:51:85:85:13:a8:54:e1:ee:76:a2:89:18:d3:97:
+         89:7a:c6:fd:b3:bd:94:61:5a:3a:08:cf:14:93:bd:93:fd:09:
+         a9:7b:56:c8:00:b8:44:58:e9:de:5b:77:bd:07:1c:6c:0b:30:
+         30:c7
+-----BEGIN CERTIFICATE-----
+MIIFEzCCBHygAwIBAgIQV7/7A/ssRtThns7g10N/EzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggHeMIIB2jAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjBt
+BggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYwITAfMAcGBSsOAwIa
+BBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9nby52ZXJpc2lnbi5j
+b20vdnNsb2dvLmdpZjA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7Lvw
+MAnzQzn6Aq8zMTMwNAYDVR0lBC0wKwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBBggr
+BgEFBQcDAQYIKwYBBQUHAwIwgYAGA1UdIwR5MHehY6RhMF8xCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJs
+aWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eYIQcLrkHRDZKTS2OMp7
+A8y6vzANBgkqhkiG9w0BAQUFAAOBgQCpe2YpMPfVtKaWEtDucvBYEWkVVV9B/9IS
+hBOk2QNm/6ngTMntjHKLtNdVOykVYMg8Ie9ELpM9xgsMjSQ/HvsBWnrdg2YU0cf9
+MFNIUYWFE6hU4e52ookY05eJesb9s72UYVo6CM8Uk72T/Qmpe1bIALhEWOneW3e9
+BxxsCzAwxw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert83[] = {
+  0x30, 0x82, 0x05, 0x13, 0x30, 0x82, 0x04, 0x7c, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x57, 0xbf, 0xfb, 0x03, 0xfb, 0x2c, 0x46, 0xd4, 0xe1,
+  0x9e, 0xce, 0xe0, 0xd7, 0x43, 0x7f, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+  0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+  0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+  0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+  0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+  0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+  0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+  0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+  0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+  0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+  0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+  0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+  0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+  0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+  0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+  0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+  0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+  0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+  0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+  0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+  0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+  0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+  0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+  0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+  0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+  0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+  0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+  0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+  0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+  0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0xde, 0x30, 0x82, 0x01, 0xda, 0x30, 0x0f,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+  0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+  0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+  0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61,
+  0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55,
+  0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30,
+  0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+  0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3,
+  0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25,
+  0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67,
+  0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69,
+  0x66, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+  0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x63, 0x70, 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0,
+  0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30,
+  0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x09,
+  0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60,
+  0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x79, 0x30, 0x77, 0xa1, 0x63, 0xa4, 0x61, 0x30, 0x5f, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+  0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2e,
+  0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c,
+  0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x82, 0x10,
+  0x70, 0xba, 0xe4, 0x1d, 0x10, 0xd9, 0x29, 0x34, 0xb6, 0x38, 0xca, 0x7b,
+  0x03, 0xcc, 0xba, 0xbf, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xa9,
+  0x7b, 0x66, 0x29, 0x30, 0xf7, 0xd5, 0xb4, 0xa6, 0x96, 0x12, 0xd0, 0xee,
+  0x72, 0xf0, 0x58, 0x11, 0x69, 0x15, 0x55, 0x5f, 0x41, 0xff, 0xd2, 0x12,
+  0x84, 0x13, 0xa4, 0xd9, 0x03, 0x66, 0xff, 0xa9, 0xe0, 0x4c, 0xc9, 0xed,
+  0x8c, 0x72, 0x8b, 0xb4, 0xd7, 0x55, 0x3b, 0x29, 0x15, 0x60, 0xc8, 0x3c,
+  0x21, 0xef, 0x44, 0x2e, 0x93, 0x3d, 0xc6, 0x0b, 0x0c, 0x8d, 0x24, 0x3f,
+  0x1e, 0xfb, 0x01, 0x5a, 0x7a, 0xdd, 0x83, 0x66, 0x14, 0xd1, 0xc7, 0xfd,
+  0x30, 0x53, 0x48, 0x51, 0x85, 0x85, 0x13, 0xa8, 0x54, 0xe1, 0xee, 0x76,
+  0xa2, 0x89, 0x18, 0xd3, 0x97, 0x89, 0x7a, 0xc6, 0xfd, 0xb3, 0xbd, 0x94,
+  0x61, 0x5a, 0x3a, 0x08, 0xcf, 0x14, 0x93, 0xbd, 0x93, 0xfd, 0x09, 0xa9,
+  0x7b, 0x56, 0xc8, 0x00, 0xb8, 0x44, 0x58, 0xe9, 0xde, 0x5b, 0x77, 0xbd,
+  0x07, 0x1c, 0x6c, 0x0b, 0x30, 0x30, 0xc7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:0a:57:82:2c:c6:f5:e1:4f:19:b7:09:55:c8:03:42
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+        Validity
+            Not Before: Dec  1 00:00:00 2006 GMT
+            Not After : Dec 31 23:59:59 2019 GMT
+        Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO High Assurance Secure Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b8:4c:b0:aa:8e:06:df:37:4a:a4:f2:08:ff:e2:
+                    b9:92:d6:6f:eb:9e:35:4e:ec:e5:65:08:8b:13:c0:
+                    bc:38:7b:11:12:1c:0b:4b:f4:37:22:15:cc:60:d1:
+                    c5:1e:28:d7:2f:9a:97:d7:1c:04:8d:9b:63:7d:6e:
+                    2f:ee:f8:1f:4b:33:3e:d8:4d:86:61:0b:5a:9b:d8:
+                    96:3b:05:76:0b:2b:cb:d7:85:21:bc:19:a7:c6:68:
+                    44:83:18:b2:17:44:b5:90:9c:65:6f:71:92:50:71:
+                    a0:55:72:26:92:5e:d3:69:eb:08:3f:f2:7e:a7:a0:
+                    b3:eb:ab:e1:03:b9:88:7a:81:3f:a5:84:dc:92:43:
+                    4e:3b:57:70:00:1e:6b:99:50:0d:53:e8:e2:b6:18:
+                    92:1a:cd:b8:4c:5e:d1:a0:c4:a0:f1:c6:ec:fc:dd:
+                    d1:7c:91:1a:14:91:32:9d:79:46:ab:f1:f0:48:60:
+                    28:55:b4:4c:e6:16:0e:bc:ef:5e:ca:d0:fe:ec:91:
+                    f0:d5:11:18:5c:aa:c3:86:67:c4:11:43:08:69:55:
+                    80:b5:b0:20:48:da:78:89:09:04:57:37:f7:5d:28:
+                    f3:47:fb:18:c9:be:be:78:b0:32:74:da:55:da:d6:
+                    54:86:3e:95:2b:15:1a:ed:94:5b:96:6a:f8:e3:c5:
+                    9d:ab
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+            X509v3 Subject Key Identifier: 
+                60:59:CD:80:C7:C5:E3:AB:8C:2F:FC:6B:E5:5B:0A:F5:0F:DE:4B:FF
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Extended Key Usage: 
+                Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://secure.comodo.com/CPS
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.comodoca.com/ComodoUTNSGCCA.crt
+                OCSP - URI:http://ocsp.comodoca.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         32:86:37:17:d4:67:f2:ee:2d:7f:a8:03:f6:df:22:c7:ee:4c:
+         0d:b1:4c:c1:27:b4:2c:5d:0d:30:07:8a:b3:41:51:e4:47:b0:
+         ca:a2:1d:6d:b9:a4:fb:96:23:87:03:b6:27:29:25:51:fe:48:
+         b5:98:c3:dc:34:47:e5:40:43:31:14:33:ef:bd:7f:99:43:ff:
+         48:68:01:de:88:44:63:27:f1:22:be:c0:2f:74:d6:57:63:d6:
+         30:c7:3f:3b:cc:bb:d2:e5:33:72:99:5a:bf:d9:33:59:b6:41:
+         83:b4:98:3a:9c:77:00:a4:f1:c4:30:e8:1d:af:e2:d6:f8:7e:
+         2a:66:45:58:81:21:8f:50:60:15:ef:60:62:d4:ab:3a:b9:f0:
+         fa:5c:e7:3c:3d:9d:b9:5f:75:c9:c8:73:af:5e:fe:03:6c:4c:
+         e6:ea:28:14:54:21:8e:99:4c:db:25:7c:cc:03:d5:81:26:fa:
+         57:42:8b:79:10:03:70:f5:6a:43:82:5c:5b:5c:1f:85:09:20:
+         42:66:71:59:d5:2f:49:b5:ab:29:63:54:e3:03:99:8d:8e:38:
+         28:44:ff:b5:3e:c3:cd:43:73:3d:d9:3a:3b:0d:bc:f6:88:21:
+         34:99:d9:99:e8:56:7c:27:84:ae:d3:c8:bd:b7:82:fa:74:2c:
+         e0:33:a6:8f
+-----BEGIN CERTIFICATE-----
+MIIFGzCCBAOgAwIBAgIQCApXgizG9eFPGbcJVcgDQjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMIGJMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEvMC0GA1UEAxMmQ09NT0RPIEhpZ2ggQXNzdXJhbmNlIFNl
+Y3VyZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4
+TLCqjgbfN0qk8gj/4rmS1m/rnjVO7OVlCIsTwLw4exESHAtL9DciFcxg0cUeKNcv
+mpfXHASNm2N9bi/u+B9LMz7YTYZhC1qb2JY7BXYLK8vXhSG8GafGaESDGLIXRLWQ
+nGVvcZJQcaBVciaSXtNp6wg/8n6noLPrq+EDuYh6gT+lhNySQ047V3AAHmuZUA1T
+6OK2GJIazbhMXtGgxKDxxuz83dF8kRoUkTKdeUar8fBIYChVtEzmFg68717K0P7s
+kfDVERhcqsOGZ8QRQwhpVYC1sCBI2niJCQRXN/ddKPNH+xjJvr54sDJ02lXa1lSG
+PpUrFRrtlFuWavjjxZ2rAgMBAAGjggGDMIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwV
+N6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQUYFnNgMfF46uML/xr5VsK9Q/eS/8wDgYD
+VR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYB
+BAGCNwoDAwYJYIZIAYb4QgQBMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUF
+BwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDBsBggrBgEFBQcBAQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6
+Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9kb1VUTlNHQ0NBLmNydDAkBggrBgEFBQcw
+AYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQAy
+hjcX1Gfy7i1/qAP23yLH7kwNsUzBJ7QsXQ0wB4qzQVHkR7DKoh1tuaT7liOHA7Yn
+KSVR/ki1mMPcNEflQEMxFDPvvX+ZQ/9IaAHeiERjJ/EivsAvdNZXY9Ywxz87zLvS
+5TNymVq/2TNZtkGDtJg6nHcApPHEMOgdr+LW+H4qZkVYgSGPUGAV72Bi1Ks6ufD6
+XOc8PZ25X3XJyHOvXv4DbEzm6igUVCGOmUzbJXzMA9WBJvpXQot5EANw9WpDglxb
+XB+FCSBCZnFZ1S9JtaspY1TjA5mNjjgoRP+1PsPNQ3M92To7Dbz2iCE0mdmZ6FZ8
+J4Su08i9t4L6dCzgM6aP
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert84[] = {
+  0x30, 0x82, 0x05, 0x1b, 0x30, 0x82, 0x04, 0x03, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x08, 0x0a, 0x57, 0x82, 0x2c, 0xc6, 0xf5, 0xe1, 0x4f,
+  0x19, 0xb7, 0x09, 0x55, 0xc8, 0x03, 0x42, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+  0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+  0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+  0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+  0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+  0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0x89, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b,
+  0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65,
+  0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73,
+  0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07,
+  0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30,
+  0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f,
+  0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65,
+  0x64, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+  0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20,
+  0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65,
+  0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20,
+  0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb8,
+  0x4c, 0xb0, 0xaa, 0x8e, 0x06, 0xdf, 0x37, 0x4a, 0xa4, 0xf2, 0x08, 0xff,
+  0xe2, 0xb9, 0x92, 0xd6, 0x6f, 0xeb, 0x9e, 0x35, 0x4e, 0xec, 0xe5, 0x65,
+  0x08, 0x8b, 0x13, 0xc0, 0xbc, 0x38, 0x7b, 0x11, 0x12, 0x1c, 0x0b, 0x4b,
+  0xf4, 0x37, 0x22, 0x15, 0xcc, 0x60, 0xd1, 0xc5, 0x1e, 0x28, 0xd7, 0x2f,
+  0x9a, 0x97, 0xd7, 0x1c, 0x04, 0x8d, 0x9b, 0x63, 0x7d, 0x6e, 0x2f, 0xee,
+  0xf8, 0x1f, 0x4b, 0x33, 0x3e, 0xd8, 0x4d, 0x86, 0x61, 0x0b, 0x5a, 0x9b,
+  0xd8, 0x96, 0x3b, 0x05, 0x76, 0x0b, 0x2b, 0xcb, 0xd7, 0x85, 0x21, 0xbc,
+  0x19, 0xa7, 0xc6, 0x68, 0x44, 0x83, 0x18, 0xb2, 0x17, 0x44, 0xb5, 0x90,
+  0x9c, 0x65, 0x6f, 0x71, 0x92, 0x50, 0x71, 0xa0, 0x55, 0x72, 0x26, 0x92,
+  0x5e, 0xd3, 0x69, 0xeb, 0x08, 0x3f, 0xf2, 0x7e, 0xa7, 0xa0, 0xb3, 0xeb,
+  0xab, 0xe1, 0x03, 0xb9, 0x88, 0x7a, 0x81, 0x3f, 0xa5, 0x84, 0xdc, 0x92,
+  0x43, 0x4e, 0x3b, 0x57, 0x70, 0x00, 0x1e, 0x6b, 0x99, 0x50, 0x0d, 0x53,
+  0xe8, 0xe2, 0xb6, 0x18, 0x92, 0x1a, 0xcd, 0xb8, 0x4c, 0x5e, 0xd1, 0xa0,
+  0xc4, 0xa0, 0xf1, 0xc6, 0xec, 0xfc, 0xdd, 0xd1, 0x7c, 0x91, 0x1a, 0x14,
+  0x91, 0x32, 0x9d, 0x79, 0x46, 0xab, 0xf1, 0xf0, 0x48, 0x60, 0x28, 0x55,
+  0xb4, 0x4c, 0xe6, 0x16, 0x0e, 0xbc, 0xef, 0x5e, 0xca, 0xd0, 0xfe, 0xec,
+  0x91, 0xf0, 0xd5, 0x11, 0x18, 0x5c, 0xaa, 0xc3, 0x86, 0x67, 0xc4, 0x11,
+  0x43, 0x08, 0x69, 0x55, 0x80, 0xb5, 0xb0, 0x20, 0x48, 0xda, 0x78, 0x89,
+  0x09, 0x04, 0x57, 0x37, 0xf7, 0x5d, 0x28, 0xf3, 0x47, 0xfb, 0x18, 0xc9,
+  0xbe, 0xbe, 0x78, 0xb0, 0x32, 0x74, 0xda, 0x55, 0xda, 0xd6, 0x54, 0x86,
+  0x3e, 0x95, 0x2b, 0x15, 0x1a, 0xed, 0x94, 0x5b, 0x96, 0x6a, 0xf8, 0xe3,
+  0xc5, 0x9d, 0xab, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x83,
+  0x30, 0x82, 0x01, 0x7f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+  0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+  0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x60, 0x59, 0xcd, 0x80, 0xc7, 0xc5, 0xe3, 0xab, 0x8c, 0x2f, 0xfc, 0x6b,
+  0xe5, 0x5b, 0x0a, 0xf5, 0x0f, 0xde, 0x4b, 0xff, 0x30, 0x0e, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x20, 0x06, 0x03,
+  0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20,
+  0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+  0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+  0x64, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0,
+  0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43,
+  0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x6c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+  0x01, 0x04, 0x60, 0x30, 0x5e, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+  0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+  0x6f, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72,
+  0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+  0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+  0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x32,
+  0x86, 0x37, 0x17, 0xd4, 0x67, 0xf2, 0xee, 0x2d, 0x7f, 0xa8, 0x03, 0xf6,
+  0xdf, 0x22, 0xc7, 0xee, 0x4c, 0x0d, 0xb1, 0x4c, 0xc1, 0x27, 0xb4, 0x2c,
+  0x5d, 0x0d, 0x30, 0x07, 0x8a, 0xb3, 0x41, 0x51, 0xe4, 0x47, 0xb0, 0xca,
+  0xa2, 0x1d, 0x6d, 0xb9, 0xa4, 0xfb, 0x96, 0x23, 0x87, 0x03, 0xb6, 0x27,
+  0x29, 0x25, 0x51, 0xfe, 0x48, 0xb5, 0x98, 0xc3, 0xdc, 0x34, 0x47, 0xe5,
+  0x40, 0x43, 0x31, 0x14, 0x33, 0xef, 0xbd, 0x7f, 0x99, 0x43, 0xff, 0x48,
+  0x68, 0x01, 0xde, 0x88, 0x44, 0x63, 0x27, 0xf1, 0x22, 0xbe, 0xc0, 0x2f,
+  0x74, 0xd6, 0x57, 0x63, 0xd6, 0x30, 0xc7, 0x3f, 0x3b, 0xcc, 0xbb, 0xd2,
+  0xe5, 0x33, 0x72, 0x99, 0x5a, 0xbf, 0xd9, 0x33, 0x59, 0xb6, 0x41, 0x83,
+  0xb4, 0x98, 0x3a, 0x9c, 0x77, 0x00, 0xa4, 0xf1, 0xc4, 0x30, 0xe8, 0x1d,
+  0xaf, 0xe2, 0xd6, 0xf8, 0x7e, 0x2a, 0x66, 0x45, 0x58, 0x81, 0x21, 0x8f,
+  0x50, 0x60, 0x15, 0xef, 0x60, 0x62, 0xd4, 0xab, 0x3a, 0xb9, 0xf0, 0xfa,
+  0x5c, 0xe7, 0x3c, 0x3d, 0x9d, 0xb9, 0x5f, 0x75, 0xc9, 0xc8, 0x73, 0xaf,
+  0x5e, 0xfe, 0x03, 0x6c, 0x4c, 0xe6, 0xea, 0x28, 0x14, 0x54, 0x21, 0x8e,
+  0x99, 0x4c, 0xdb, 0x25, 0x7c, 0xcc, 0x03, 0xd5, 0x81, 0x26, 0xfa, 0x57,
+  0x42, 0x8b, 0x79, 0x10, 0x03, 0x70, 0xf5, 0x6a, 0x43, 0x82, 0x5c, 0x5b,
+  0x5c, 0x1f, 0x85, 0x09, 0x20, 0x42, 0x66, 0x71, 0x59, 0xd5, 0x2f, 0x49,
+  0xb5, 0xab, 0x29, 0x63, 0x54, 0xe3, 0x03, 0x99, 0x8d, 0x8e, 0x38, 0x28,
+  0x44, 0xff, 0xb5, 0x3e, 0xc3, 0xcd, 0x43, 0x73, 0x3d, 0xd9, 0x3a, 0x3b,
+  0x0d, 0xbc, 0xf6, 0x88, 0x21, 0x34, 0x99, 0xd9, 0x99, 0xe8, 0x56, 0x7c,
+  0x27, 0x84, 0xae, 0xd3, 0xc8, 0xbd, 0xb7, 0x82, 0xfa, 0x74, 0x2c, 0xe0,
+  0x33, 0xa6, 0x8f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            03:0e:95:29:4d:ae:c1:2c:03:cf:31:ab:5b:02:71:d7
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+        Validity
+            Not Before: May 30 10:48:38 2000 GMT
+            Not After : May 30 10:48:38 2020 GMT
+        Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e4:bc:7e:92:30:6d:c6:d8:8e:2b:0b:bc:46:ce:
+                    e0:27:96:de:de:f9:fa:12:d3:3c:33:73:b3:04:2f:
+                    bc:71:8c:e5:9f:b6:22:60:3e:5f:5d:ce:09:ff:82:
+                    0c:1b:9a:51:50:1a:26:89:dd:d5:61:5d:19:dc:12:
+                    0f:2d:0a:a2:43:5d:17:d0:34:92:20:ea:73:cf:38:
+                    2c:06:26:09:7a:72:f7:fa:50:32:f8:c2:93:d3:69:
+                    a2:23:ce:41:b1:cc:e4:d5:1f:36:d1:8a:3a:f8:8c:
+                    63:e2:14:59:69:ed:0d:d3:7f:6b:e8:b8:03:e5:4f:
+                    6a:e5:98:63:69:48:05:be:2e:ff:33:b6:e9:97:59:
+                    69:f8:67:19:ae:93:61:96:44:15:d3:72:b0:3f:bc:
+                    6a:7d:ec:48:7f:8d:c3:ab:aa:71:2b:53:69:41:53:
+                    34:b5:b0:b9:c5:06:0a:c4:b0:45:f5:41:5d:6e:89:
+                    45:7b:3d:3b:26:8c:74:c2:e5:d2:d1:7d:b2:11:d4:
+                    fb:58:32:22:9a:80:c9:dc:fd:0c:e9:7f:5e:03:97:
+                    ce:3b:00:14:87:27:70:38:a9:8e:6e:b3:27:76:98:
+                    51:e0:05:e3:21:ab:1a:d5:85:22:3c:29:b5:9a:16:
+                    c5:80:a8:f4:bb:6b:30:8f:2f:46:02:a2:b1:0c:22:
+                    e0:d3
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Authority Key Identifier: 
+                keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+            X509v3 Subject Key Identifier: 
+                21:30:C9:FB:00:D7:4E:98:DA:87:AA:2A:D0:A7:2E:B1:40:31:A7:4C
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.782.1.2.1.8.1
+                  CPS: http://www.networksolutions.com/legal/SSL-legal-repository-ev-cps.jsp
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+                CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+                OCSP - URI:http://ocsp.usertrust.com
+
+    Signature Algorithm: sha1WithRSAEncryption
+         56:45:d5:72:11:a7:cc:89:b3:32:5a:90:8c:53:73:05:36:c4:
+         33:27:af:3e:41:c0:e9:72:2d:49:9d:52:1e:55:5a:9e:87:d0:
+         a9:4e:f0:5c:0c:9d:d2:3a:0f:8a:e7:4a:cc:5e:ba:a5:b5:9f:
+         c9:ab:3d:99:4c:46:86:7c:c0:35:8e:17:7b:5d:4a:21:2b:6e:
+         2c:67:fd:1f:66:3e:c8:5d:4b:0c:d3:cd:ef:51:74:c1:00:b7:
+         c7:ac:b0:12:d5:77:20:f9:f0:f3:e2:f4:e7:e2:f6:07:fd:1d:
+         94:f8:e5:3d:83:1d:37:2a:93:aa:2c:7a:99:d6:62:90:11:80:
+         23:08:f8:62:cc:1d:27:47:d4:53:97:1c:17:52:10:70:07:22:
+         3a:ec:9a:37:d1:19:1e:d4:20:8e:6f:9f:44:7f:3a:11:ab:6b:
+         9b:ce:81:4f:c4:8e:ee:3c:b0:27:4a:1f:9c:79:d1:91:bf:73:
+         f0:dd:b2:00:5c:33:ee:59:61:fd:ae:27:72:f3:b1:3d:7a:ff:
+         4c:41:92:e4:83:e2:56:a2:44:e3:03:56:f6:34:9a:36:7b:62:
+         96:22:f3:6f:d3:4e:7f:2c:b2:b0:b2:5b:b1:2a:06:0e:36:5b:
+         63:34:c8:3b:69:b3:ef:51:ac:9a:68:85:ed:2e:2d:44:fe:9e:
+         09:d7:26:f8
+-----BEGIN CERTIFICATE-----
+MIIFLjCCBBagAwIBAgIQAw6VKU2uwSwDzzGrWwJx1zANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+YjELMAkGA1UEBhMCVVMxITAfBgNVBAoTGE5ldHdvcmsgU29sdXRpb25zIEwuTC5D
+LjEwMC4GA1UEAxMnTmV0d29yayBTb2x1dGlvbnMgQ2VydGlmaWNhdGUgQXV0aG9y
+aXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5Lx+kjBtxtiOKwu8
+Rs7gJ5be3vn6EtM8M3OzBC+8cYzln7YiYD5fXc4J/4IMG5pRUBomid3VYV0Z3BIP
+LQqiQ10X0DSSIOpzzzgsBiYJenL3+lAy+MKT02miI85Bsczk1R820Yo6+Ixj4hRZ
+ae0N039r6LgD5U9q5ZhjaUgFvi7/M7bpl1lp+GcZrpNhlkQV03KwP7xqfexIf43D
+q6pxK1NpQVM0tbC5xQYKxLBF9UFdbolFez07Jox0wuXS0X2yEdT7WDIimoDJ3P0M
+6X9eA5fOOwAUhydwOKmObrMndphR4AXjIasa1YUiPCm1mhbFgKj0u2swjy9GAqKx
+DCLg0wIDAQABo4IB0TCCAc0wHwYDVR0jBBgwFoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBowHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MG4GA1UdIARnMGUwYwYMKwYBBAGGDgECAQgBMFMw
+UQYIKwYBBQUHAgEWRWh0dHA6Ly93d3cubmV0d29ya3NvbHV0aW9ucy5jb20vbGVn
+YWwvU1NMLWxlZ2FsLXJlcG9zaXRvcnktZXYtY3BzLmpzcDBEBgNVHR8EPTA7MDmg
+N6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENB
+Um9vdC5jcmwwgbMGCCsGAQUFBwEBBIGmMIGjMD8GCCsGAQUFBzAChjNodHRwOi8v
+Y3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5wN2MwOQYI
+KwYBBQUHMAKGLWh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdFVUTlNH
+Q0NBLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTAN
+BgkqhkiG9w0BAQUFAAOCAQEAVkXVchGnzImzMlqQjFNzBTbEMyevPkHA6XItSZ1S
+HlVanofQqU7wXAyd0joPiudKzF66pbWfyas9mUxGhnzANY4Xe11KIStuLGf9H2Y+
+yF1LDNPN71F0wQC3x6ywEtV3IPnw8+L05+L2B/0dlPjlPYMdNyqTqix6mdZikBGA
+Iwj4YswdJ0fUU5ccF1IQcAciOuyaN9EZHtQgjm+fRH86Eatrm86BT8SO7jywJ0of
+nHnRkb9z8N2yAFwz7llh/a4ncvOxPXr/TEGS5IPiVqJE4wNW9jSaNntiliLzb9NO
+fyyysLJbsSoGDjZbYzTIO2mz71GsmmiF7S4tRP6eCdcm+A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert85[] = {
+  0x30, 0x82, 0x05, 0x2e, 0x30, 0x82, 0x04, 0x16, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x03, 0x0e, 0x95, 0x29, 0x4d, 0xae, 0xc1, 0x2c, 0x03,
+  0xcf, 0x31, 0xab, 0x5b, 0x02, 0x71, 0xd7, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+  0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+  0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+  0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+  0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+  0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+  0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+  0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x35, 0x33,
+  0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+  0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c,
+  0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43,
+  0x2e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27,
+  0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+  0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xe4, 0xbc, 0x7e, 0x92, 0x30, 0x6d, 0xc6, 0xd8, 0x8e, 0x2b, 0x0b, 0xbc,
+  0x46, 0xce, 0xe0, 0x27, 0x96, 0xde, 0xde, 0xf9, 0xfa, 0x12, 0xd3, 0x3c,
+  0x33, 0x73, 0xb3, 0x04, 0x2f, 0xbc, 0x71, 0x8c, 0xe5, 0x9f, 0xb6, 0x22,
+  0x60, 0x3e, 0x5f, 0x5d, 0xce, 0x09, 0xff, 0x82, 0x0c, 0x1b, 0x9a, 0x51,
+  0x50, 0x1a, 0x26, 0x89, 0xdd, 0xd5, 0x61, 0x5d, 0x19, 0xdc, 0x12, 0x0f,
+  0x2d, 0x0a, 0xa2, 0x43, 0x5d, 0x17, 0xd0, 0x34, 0x92, 0x20, 0xea, 0x73,
+  0xcf, 0x38, 0x2c, 0x06, 0x26, 0x09, 0x7a, 0x72, 0xf7, 0xfa, 0x50, 0x32,
+  0xf8, 0xc2, 0x93, 0xd3, 0x69, 0xa2, 0x23, 0xce, 0x41, 0xb1, 0xcc, 0xe4,
+  0xd5, 0x1f, 0x36, 0xd1, 0x8a, 0x3a, 0xf8, 0x8c, 0x63, 0xe2, 0x14, 0x59,
+  0x69, 0xed, 0x0d, 0xd3, 0x7f, 0x6b, 0xe8, 0xb8, 0x03, 0xe5, 0x4f, 0x6a,
+  0xe5, 0x98, 0x63, 0x69, 0x48, 0x05, 0xbe, 0x2e, 0xff, 0x33, 0xb6, 0xe9,
+  0x97, 0x59, 0x69, 0xf8, 0x67, 0x19, 0xae, 0x93, 0x61, 0x96, 0x44, 0x15,
+  0xd3, 0x72, 0xb0, 0x3f, 0xbc, 0x6a, 0x7d, 0xec, 0x48, 0x7f, 0x8d, 0xc3,
+  0xab, 0xaa, 0x71, 0x2b, 0x53, 0x69, 0x41, 0x53, 0x34, 0xb5, 0xb0, 0xb9,
+  0xc5, 0x06, 0x0a, 0xc4, 0xb0, 0x45, 0xf5, 0x41, 0x5d, 0x6e, 0x89, 0x45,
+  0x7b, 0x3d, 0x3b, 0x26, 0x8c, 0x74, 0xc2, 0xe5, 0xd2, 0xd1, 0x7d, 0xb2,
+  0x11, 0xd4, 0xfb, 0x58, 0x32, 0x22, 0x9a, 0x80, 0xc9, 0xdc, 0xfd, 0x0c,
+  0xe9, 0x7f, 0x5e, 0x03, 0x97, 0xce, 0x3b, 0x00, 0x14, 0x87, 0x27, 0x70,
+  0x38, 0xa9, 0x8e, 0x6e, 0xb3, 0x27, 0x76, 0x98, 0x51, 0xe0, 0x05, 0xe3,
+  0x21, 0xab, 0x1a, 0xd5, 0x85, 0x22, 0x3c, 0x29, 0xb5, 0x9a, 0x16, 0xc5,
+  0x80, 0xa8, 0xf4, 0xbb, 0x6b, 0x30, 0x8f, 0x2f, 0x46, 0x02, 0xa2, 0xb1,
+  0x0c, 0x22, 0xe0, 0xd3, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+  0xd1, 0x30, 0x82, 0x01, 0xcd, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4,
+  0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb,
+  0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
+  0x14, 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, 0xaa,
+  0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+  0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x6e, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x67, 0x30, 0x65, 0x30, 0x63, 0x06, 0x0c, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x86, 0x0e, 0x01, 0x02, 0x01, 0x08, 0x01, 0x30, 0x53, 0x30,
+  0x51, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+  0x45, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x6f, 0x6c, 0x75, 0x74,
+  0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x67,
+  0x61, 0x6c, 0x2f, 0x53, 0x53, 0x4c, 0x2d, 0x6c, 0x65, 0x67, 0x61, 0x6c,
+  0x2d, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2d,
+  0x65, 0x76, 0x2d, 0x63, 0x70, 0x73, 0x2e, 0x6a, 0x73, 0x70, 0x30, 0x44,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+  0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+  0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6,
+  0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+  0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+  0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+  0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47,
+  0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65,
+  0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x56, 0x45, 0xd5, 0x72, 0x11, 0xa7,
+  0xcc, 0x89, 0xb3, 0x32, 0x5a, 0x90, 0x8c, 0x53, 0x73, 0x05, 0x36, 0xc4,
+  0x33, 0x27, 0xaf, 0x3e, 0x41, 0xc0, 0xe9, 0x72, 0x2d, 0x49, 0x9d, 0x52,
+  0x1e, 0x55, 0x5a, 0x9e, 0x87, 0xd0, 0xa9, 0x4e, 0xf0, 0x5c, 0x0c, 0x9d,
+  0xd2, 0x3a, 0x0f, 0x8a, 0xe7, 0x4a, 0xcc, 0x5e, 0xba, 0xa5, 0xb5, 0x9f,
+  0xc9, 0xab, 0x3d, 0x99, 0x4c, 0x46, 0x86, 0x7c, 0xc0, 0x35, 0x8e, 0x17,
+  0x7b, 0x5d, 0x4a, 0x21, 0x2b, 0x6e, 0x2c, 0x67, 0xfd, 0x1f, 0x66, 0x3e,
+  0xc8, 0x5d, 0x4b, 0x0c, 0xd3, 0xcd, 0xef, 0x51, 0x74, 0xc1, 0x00, 0xb7,
+  0xc7, 0xac, 0xb0, 0x12, 0xd5, 0x77, 0x20, 0xf9, 0xf0, 0xf3, 0xe2, 0xf4,
+  0xe7, 0xe2, 0xf6, 0x07, 0xfd, 0x1d, 0x94, 0xf8, 0xe5, 0x3d, 0x83, 0x1d,
+  0x37, 0x2a, 0x93, 0xaa, 0x2c, 0x7a, 0x99, 0xd6, 0x62, 0x90, 0x11, 0x80,
+  0x23, 0x08, 0xf8, 0x62, 0xcc, 0x1d, 0x27, 0x47, 0xd4, 0x53, 0x97, 0x1c,
+  0x17, 0x52, 0x10, 0x70, 0x07, 0x22, 0x3a, 0xec, 0x9a, 0x37, 0xd1, 0x19,
+  0x1e, 0xd4, 0x20, 0x8e, 0x6f, 0x9f, 0x44, 0x7f, 0x3a, 0x11, 0xab, 0x6b,
+  0x9b, 0xce, 0x81, 0x4f, 0xc4, 0x8e, 0xee, 0x3c, 0xb0, 0x27, 0x4a, 0x1f,
+  0x9c, 0x79, 0xd1, 0x91, 0xbf, 0x73, 0xf0, 0xdd, 0xb2, 0x00, 0x5c, 0x33,
+  0xee, 0x59, 0x61, 0xfd, 0xae, 0x27, 0x72, 0xf3, 0xb1, 0x3d, 0x7a, 0xff,
+  0x4c, 0x41, 0x92, 0xe4, 0x83, 0xe2, 0x56, 0xa2, 0x44, 0xe3, 0x03, 0x56,
+  0xf6, 0x34, 0x9a, 0x36, 0x7b, 0x62, 0x96, 0x22, 0xf3, 0x6f, 0xd3, 0x4e,
+  0x7f, 0x2c, 0xb2, 0xb0, 0xb2, 0x5b, 0xb1, 0x2a, 0x06, 0x0e, 0x36, 0x5b,
+  0x63, 0x34, 0xc8, 0x3b, 0x69, 0xb3, 0xef, 0x51, 0xac, 0x9a, 0x68, 0x85,
+  0xed, 0x2e, 0x2d, 0x44, 0xfe, 0x9e, 0x09, 0xd7, 0x26, 0xf8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            5f:a6:be:80:b6:86:c6:2f:01:ed:0c:ab:b1:96:a1:05
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+        Validity
+            Not Before: Nov 17 00:00:00 2006 GMT
+            Not After : Dec 30 23:59:59 2020 GMT
+        Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ac:a0:f0:fb:80:59:d4:9c:c7:a4:cf:9d:a1:59:
+                    73:09:10:45:0c:0d:2c:6e:68:f1:6c:5b:48:68:49:
+                    59:37:fc:0b:33:19:c2:77:7f:cc:10:2d:95:34:1c:
+                    e6:eb:4d:09:a7:1c:d2:b8:c9:97:36:02:b7:89:d4:
+                    24:5f:06:c0:cc:44:94:94:8d:02:62:6f:eb:5a:dd:
+                    11:8d:28:9a:5c:84:90:10:7a:0d:bd:74:66:2f:6a:
+                    38:a0:e2:d5:54:44:eb:1d:07:9f:07:ba:6f:ee:e9:
+                    fd:4e:0b:29:f5:3e:84:a0:01:f1:9c:ab:f8:1c:7e:
+                    89:a4:e8:a1:d8:71:65:0d:a3:51:7b:ee:bc:d2:22:
+                    60:0d:b9:5b:9d:df:ba:fc:51:5b:0b:af:98:b2:e9:
+                    2e:e9:04:e8:62:87:de:2b:c8:d7:4e:c1:4c:64:1e:
+                    dd:cf:87:58:ba:4a:4f:ca:68:07:1d:1c:9d:4a:c6:
+                    d5:2f:91:cc:7c:71:72:1c:c5:c0:67:eb:32:fd:c9:
+                    92:5c:94:da:85:c0:9b:bf:53:7d:2b:09:f4:8c:9d:
+                    91:1f:97:6a:52:cb:de:09:36:a4:77:d8:7b:87:50:
+                    44:d5:3e:6e:29:69:fb:39:49:26:1e:09:a5:80:7b:
+                    40:2d:eb:e8:27:85:c9:fe:61:fd:7e:e6:7c:97:1d:
+                    d5:9d
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.thawte.com/cps
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.thawte.com/ThawtePremiumServerCA.crl
+
+            X509v3 Extended Key Usage: 
+                Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+            X509v3 Authority Key Identifier: 
+                DirName:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+                serial:01
+
+    Signature Algorithm: sha1WithRSAEncryption
+         2b:ca:12:c9:dd:d7:cc:63:1c:9b:31:35:4a:dd:e4:b7:f6:9d:
+         d1:a4:fb:1e:f8:47:f9:ae:07:8e:0d:58:12:fb:da:ed:b5:cc:
+         33:e5:97:68:47:61:42:d5:66:a9:6e:1e:47:bf:85:db:7d:58:
+         d1:77:5a:cc:90:61:98:9a:29:f5:9d:b1:cf:b8:dc:f3:7b:80:
+         47:48:d1:7d:f4:68:8c:c4:41:cb:b4:e9:fd:f0:23:e0:b1:9b:
+         76:2a:6d:28:56:a3:8c:cd:e9:ec:21:00:71:f0:5f:dd:50:a5:
+         69:42:1b:83:11:5d:84:28:d3:27:ae:ec:2a:ab:2f:60:42:c5:
+         c4:78
+-----BEGIN CERTIFICATE-----
+MIIFUTCCBLqgAwIBAgIQX6a+gLaGxi8B7QyrsZahBTANBgkqhkiG9w0BAQUFADCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTA2MTExNzAwMDAwMFoXDTIwMTIzMDIzNTk1OVow
+gakxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xKDAmBgNVBAsT
+H0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xODA2BgNVBAsTLyhjKSAy
+MDA2IHRoYXd0ZSwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQDExZ0aGF3dGUgUHJpbWFyeSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEArKDw+4BZ1JzHpM+doVlzCRBFDA0sbmjxbFtIaElZN/wLMxnC
+d3/MEC2VNBzm600JpxzSuMmXNgK3idQkXwbAzESUlI0CYm/rWt0RjSiaXISQEHoN
+vXRmL2o4oOLVVETrHQefB7pv7un9Tgsp9T6EoAHxnKv4HH6JpOih2HFlDaNRe+68
+0iJgDblbnd+6/FFbC6+Ysuku6QToYofeK8jXTsFMZB7dz4dYukpPymgHHRydSsbV
+L5HMfHFyHMXAZ+sy/cmSXJTahcCbv1N9Kwn0jJ2RH5dqUsveCTakd9h7h1BE1T5u
+KWn7OUkmHgmlgHtALevoJ4XJ/mH9fuZ8lx3VnQIDAQABo4IBzTCCAckwDwYDVR0T
+AQH/BAUwAwEB/zA7BgNVHSAENDAyMDAGBFUdIAAwKDAmBggrBgEFBQcCARYaaHR0
+cHM6Ly93d3cudGhhd3RlLmNvbS9jcHMwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW
+BBR7W0XPr87Lev0xkhpqtvNG61dIUDBABgNVHR8EOTA3MDWgM6Axhi9odHRwOi8v
+Y3JsLnRoYXd0ZS5jb20vVGhhd3RlUHJlbWl1bVNlcnZlckNBLmNybDAgBgNVHSUE
+GTAXBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwgeUGA1UdIwSB3TCB2qGB1KSB0TCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tggEBMA0GCSqGSIb3DQEBBQUAA4GBACvKEsnd18xjHJsx
+NUrd5Lf2ndGk+x74R/muB44NWBL72u21zDPll2hHYULVZqluHke/hdt9WNF3WsyQ
+YZiaKfWdsc+43PN7gEdI0X30aIzEQcu06f3wI+Cxm3YqbShWo4zN6ewhAHHwX91Q
+pWlCG4MRXYQo0yeu7CqrL2BCxcR4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert86[] = {
+  0x30, 0x82, 0x05, 0x51, 0x30, 0x82, 0x04, 0xba, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x5f, 0xa6, 0xbe, 0x80, 0xb6, 0x86, 0xc6, 0x2f, 0x01,
+  0xed, 0x0c, 0xab, 0xb1, 0x96, 0xa1, 0x05, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+  0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+  0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+  0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+  0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+  0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+  0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+  0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+  0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+  0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31,
+  0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+  0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30,
+  0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e,
+  0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20,
+  0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32,
+  0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+  0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+  0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74,
+  0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d,
+  0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1,
+  0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2,
+  0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09,
+  0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24,
+  0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb,
+  0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d,
+  0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb,
+  0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29,
+  0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89,
+  0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc,
+  0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b,
+  0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde,
+  0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58,
+  0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5,
+  0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32,
+  0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d,
+  0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde,
+  0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e,
+  0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40,
+  0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c,
+  0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+  0xcd, 0x30, 0x82, 0x01, 0xc9, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b,
+  0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06,
+  0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61,
+  0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30,
+  0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+  0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31,
+  0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0,
+  0x33, 0xa0, 0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65,
+  0x6d, 0x69, 0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+  0x19, 0x30, 0x17, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+  0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+  0x08, 0x01, 0x30, 0x81, 0xe5, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81,
+  0xdd, 0x30, 0x81, 0xda, 0xa1, 0x81, 0xd4, 0xa4, 0x81, 0xd1, 0x30, 0x81,
+  0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+  0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+  0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+  0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+  0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+  0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+  0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+  0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+  0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+  0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+  0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+  0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+  0x63, 0x6f, 0x6d, 0x82, 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+  0x00, 0x2b, 0xca, 0x12, 0xc9, 0xdd, 0xd7, 0xcc, 0x63, 0x1c, 0x9b, 0x31,
+  0x35, 0x4a, 0xdd, 0xe4, 0xb7, 0xf6, 0x9d, 0xd1, 0xa4, 0xfb, 0x1e, 0xf8,
+  0x47, 0xf9, 0xae, 0x07, 0x8e, 0x0d, 0x58, 0x12, 0xfb, 0xda, 0xed, 0xb5,
+  0xcc, 0x33, 0xe5, 0x97, 0x68, 0x47, 0x61, 0x42, 0xd5, 0x66, 0xa9, 0x6e,
+  0x1e, 0x47, 0xbf, 0x85, 0xdb, 0x7d, 0x58, 0xd1, 0x77, 0x5a, 0xcc, 0x90,
+  0x61, 0x98, 0x9a, 0x29, 0xf5, 0x9d, 0xb1, 0xcf, 0xb8, 0xdc, 0xf3, 0x7b,
+  0x80, 0x47, 0x48, 0xd1, 0x7d, 0xf4, 0x68, 0x8c, 0xc4, 0x41, 0xcb, 0xb4,
+  0xe9, 0xfd, 0xf0, 0x23, 0xe0, 0xb1, 0x9b, 0x76, 0x2a, 0x6d, 0x28, 0x56,
+  0xa3, 0x8c, 0xcd, 0xe9, 0xec, 0x21, 0x00, 0x71, 0xf0, 0x5f, 0xdd, 0x50,
+  0xa5, 0x69, 0x42, 0x1b, 0x83, 0x11, 0x5d, 0x84, 0x28, 0xd3, 0x27, 0xae,
+  0xec, 0x2a, 0xab, 0x2f, 0x60, 0x42, 0xc5, 0xc4, 0x78,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            5b:77:59:c6:17:84:e1:5e:c7:27:c0:32:95:29:28:6b
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2016 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:98:db:a0:55:eb:9c:fd:17:79:e3:9a:6e:14:1d:
+                    b1:5b:98:23:87:16:6e:87:76:9c:b5:38:3b:b5:a0:
+                    7a:b4:07:63:09:19:e6:2a:88:48:a9:e7:9d:b6:30:
+                    5a:08:97:0c:ec:aa:e4:16:69:72:62:23:9a:fb:7a:
+                    54:28:98:c5:0c:2d:b7:d7:22:b6:c8:f9:38:17:c7:
+                    dd:da:31:46:9a:94:14:8e:9e:ee:78:a0:b7:22:d4:
+                    49:54:97:4d:e5:74:5b:92:bc:ec:6c:2c:df:e7:c1:
+                    b6:1b:1a:55:6b:66:08:03:7f:45:af:9a:33:f1:10:
+                    c0:6c:99:4a:92:24:31:08:6d:dd:02:3e:61:76:78:
+                    78:b6:ed:7e:37:ae:6c:f3:89:e1:b7:e1:dc:15:cc:
+                    b7:56:9f:80:a0:b1:05:7f:4e:37:15:ff:b7:2f:1e:
+                    8f:06:38:3f:50:b7:69:28:a3:b5:66:5f:36:1a:52:
+                    48:43:66:52:df:a2:92:4f:d3:18:60:be:e3:ea:5e:
+                    19:71:05:bf:9e:1c:6c:68:72:25:6f:b3:7b:73:c9:
+                    6d:bd:12:ff:9b:41:32:5e:f4:e8:7e:c5:0b:a3:4c:
+                    64:d1:4e:bc:26:08:65:fb:19:97:58:78:e1:33:bf:
+                    ed:68:3e:b1:27:45:6f:c0:e2:ec:97:69:f7:5c:d3:
+                    f7:51
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                FC:8A:50:BA:9E:B9:25:5A:7B:55:85:4F:95:00:63:8F:E9:58:6B:43
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            Netscape Cert Type: 
+                SSL CA, S/MIME CA
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=Class3CA2048-1-47
+            Authority Information Access: 
+                OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+    Signature Algorithm: sha1WithRSAEncryption
+         96:a2:fa:7f:e6:3d:ed:d4:2b:ce:b7:15:3f:c0:72:03:5f:8b:
+         ba:16:90:25:f7:c2:83:d8:c7:75:34:63:68:12:53:0c:53:89:
+         7b:c9:56:09:a7:c3:36:44:4e:0e:d0:62:62:b3:86:fa:e8:a1:
+         9b:34:67:8d:53:22:17:3e:fd:ac:ee:67:2e:43:e2:5d:7f:33:
+         84:f2:a2:70:c0:6e:82:97:c0:34:fd:25:c6:23:7f:ed:e6:b0:
+         c5:57:43:84:b2:de:2d:f1:d0:f6:48:1f:14:71:57:b2:ac:31:
+         e1:97:24:23:c9:13:5d:74:e5:46:ef:09:7c:9e:e1:99:31:0a:
+         08:79:1b:8f:71:9f:17:66:c8:38:cf:ee:8c:97:b6:06:b9:73:
+         46:e4:d3:94:c1:e5:60:b5:25:75:2d:d9:69:31:ec:cd:96:c3:
+         a3:76:fd:e8:74:44:ac:12:b9:4d:bf:51:e8:b9:d4:44:4e:27:
+         cb:ae:20:d1:7e:2a:7c:b6:63:47:9e:76:ba:97:d0:16:e7:0b:
+         6c:6d:f7:43:6f:33:0b:29:30:77:fa:9d:f9:f5:4e:b8:76:b3:
+         cd:18:b4:f9:20:ef:3d:db:e6:ca:ad:9b:d0:4e:d2:87:a9:0d:
+         a6:44:73:50:dd:70:5b:ed:ad:7e:4a:bc:22:d5:a8:26:e4:c2:
+         85:20:0d:d9
+-----BEGIN CERTIFICATE-----
+MIIF5DCCBMygAwIBAgIQW3dZxheE4V7HJ8AylSkoazANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBujEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE0MDIGA1UEAxMrVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJjboFXrnP0XeeOabhQdsVuYI4cWbod2
+nLU4O7WgerQHYwkZ5iqISKnnnbYwWgiXDOyq5BZpcmIjmvt6VCiYxQwtt9citsj5
+OBfH3doxRpqUFI6e7nigtyLUSVSXTeV0W5K87Gws3+fBthsaVWtmCAN/Ra+aM/EQ
+wGyZSpIkMQht3QI+YXZ4eLbtfjeubPOJ4bfh3BXMt1afgKCxBX9ONxX/ty8ejwY4
+P1C3aSijtWZfNhpSSENmUt+ikk/TGGC+4+peGXEFv54cbGhyJW+ze3PJbb0S/5tB
+Ml706H7FC6NMZNFOvCYIZfsZl1h44TO/7Wg+sSdFb8Di7Jdp91zT91ECAwEAAaOC
+AdIwggHOMB0GA1UdDgQWBBT8ilC6nrklWntVhU+VAGOP6VhrQzASBgNVHRMBAf8E
+CDAGAQH/AgEAMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRw
+czovL3d3dy52ZXJpc2lnbi5jb20vY3BzMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6
+Ly9FVlNlY3VyZS1jcmwudmVyaXNpZ24uY29tL3BjYTMtZzUuY3JsMA4GA1UdDwEB
+/wQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZ
+MFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7
+GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwKQYDVR0R
+BCIwIKQeMBwxGjAYBgNVBAMTEUNsYXNzM0NBMjA0OC0xLTQ3MD0GCCsGAQUFBwEB
+BDEwLzAtBggrBgEFBQcwAYYhaHR0cDovL0VWU2VjdXJlLW9jc3AudmVyaXNpZ24u
+Y29tMB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqGSIb3DQEB
+BQUAA4IBAQCWovp/5j3t1CvOtxU/wHIDX4u6FpAl98KD2Md1NGNoElMMU4l7yVYJ
+p8M2RE4O0GJis4b66KGbNGeNUyIXPv2s7mcuQ+JdfzOE8qJwwG6Cl8A0/SXGI3/t
+5rDFV0OEst4t8dD2SB8UcVeyrDHhlyQjyRNddOVG7wl8nuGZMQoIeRuPcZ8XZsg4
+z+6Ml7YGuXNG5NOUweVgtSV1LdlpMezNlsOjdv3odESsErlNv1HoudRETifLriDR
+fip8tmNHnna6l9AW5wtsbfdDbzMLKTB3+p359U64drPNGLT5IO892+bKrZvQTtKH
+qQ2mRHNQ3XBb7a1+Srwi1agm5MKFIA3Z
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert87[] = {
+  0x30, 0x82, 0x05, 0xe4, 0x30, 0x82, 0x04, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x5b, 0x77, 0x59, 0xc6, 0x17, 0x84, 0xe1, 0x5e, 0xc7,
+  0x27, 0xc0, 0x32, 0x95, 0x29, 0x28, 0x6b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+  0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+  0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x34,
+  0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+  0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0x98, 0xdb, 0xa0, 0x55, 0xeb, 0x9c, 0xfd, 0x17, 0x79, 0xe3, 0x9a,
+  0x6e, 0x14, 0x1d, 0xb1, 0x5b, 0x98, 0x23, 0x87, 0x16, 0x6e, 0x87, 0x76,
+  0x9c, 0xb5, 0x38, 0x3b, 0xb5, 0xa0, 0x7a, 0xb4, 0x07, 0x63, 0x09, 0x19,
+  0xe6, 0x2a, 0x88, 0x48, 0xa9, 0xe7, 0x9d, 0xb6, 0x30, 0x5a, 0x08, 0x97,
+  0x0c, 0xec, 0xaa, 0xe4, 0x16, 0x69, 0x72, 0x62, 0x23, 0x9a, 0xfb, 0x7a,
+  0x54, 0x28, 0x98, 0xc5, 0x0c, 0x2d, 0xb7, 0xd7, 0x22, 0xb6, 0xc8, 0xf9,
+  0x38, 0x17, 0xc7, 0xdd, 0xda, 0x31, 0x46, 0x9a, 0x94, 0x14, 0x8e, 0x9e,
+  0xee, 0x78, 0xa0, 0xb7, 0x22, 0xd4, 0x49, 0x54, 0x97, 0x4d, 0xe5, 0x74,
+  0x5b, 0x92, 0xbc, 0xec, 0x6c, 0x2c, 0xdf, 0xe7, 0xc1, 0xb6, 0x1b, 0x1a,
+  0x55, 0x6b, 0x66, 0x08, 0x03, 0x7f, 0x45, 0xaf, 0x9a, 0x33, 0xf1, 0x10,
+  0xc0, 0x6c, 0x99, 0x4a, 0x92, 0x24, 0x31, 0x08, 0x6d, 0xdd, 0x02, 0x3e,
+  0x61, 0x76, 0x78, 0x78, 0xb6, 0xed, 0x7e, 0x37, 0xae, 0x6c, 0xf3, 0x89,
+  0xe1, 0xb7, 0xe1, 0xdc, 0x15, 0xcc, 0xb7, 0x56, 0x9f, 0x80, 0xa0, 0xb1,
+  0x05, 0x7f, 0x4e, 0x37, 0x15, 0xff, 0xb7, 0x2f, 0x1e, 0x8f, 0x06, 0x38,
+  0x3f, 0x50, 0xb7, 0x69, 0x28, 0xa3, 0xb5, 0x66, 0x5f, 0x36, 0x1a, 0x52,
+  0x48, 0x43, 0x66, 0x52, 0xdf, 0xa2, 0x92, 0x4f, 0xd3, 0x18, 0x60, 0xbe,
+  0xe3, 0xea, 0x5e, 0x19, 0x71, 0x05, 0xbf, 0x9e, 0x1c, 0x6c, 0x68, 0x72,
+  0x25, 0x6f, 0xb3, 0x7b, 0x73, 0xc9, 0x6d, 0xbd, 0x12, 0xff, 0x9b, 0x41,
+  0x32, 0x5e, 0xf4, 0xe8, 0x7e, 0xc5, 0x0b, 0xa3, 0x4c, 0x64, 0xd1, 0x4e,
+  0xbc, 0x26, 0x08, 0x65, 0xfb, 0x19, 0x97, 0x58, 0x78, 0xe1, 0x33, 0xbf,
+  0xed, 0x68, 0x3e, 0xb1, 0x27, 0x45, 0x6f, 0xc0, 0xe2, 0xec, 0x97, 0x69,
+  0xf7, 0x5c, 0xd3, 0xf7, 0x51, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0xd2, 0x30, 0x82, 0x01, 0xce, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+  0x0e, 0x04, 0x16, 0x04, 0x14, 0xfc, 0x8a, 0x50, 0xba, 0x9e, 0xb9, 0x25,
+  0x5a, 0x7b, 0x55, 0x85, 0x4f, 0x95, 0x00, 0x63, 0x8f, 0xe9, 0x58, 0x6b,
+  0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+  0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3d, 0x06,
+  0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x04,
+  0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70,
+  0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73,
+  0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x36, 0x30, 0x34, 0x30,
+  0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x63,
+  0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+  0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x11, 0x06, 0x09, 0x60,
+  0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+  0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+  0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+  0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+  0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+  0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+  0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11,
+  0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43, 0x6c, 0x61, 0x73, 0x73,
+  0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d, 0x34, 0x37,
+  0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, 0x63,
+  0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+  0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb,
+  0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x96, 0xa2, 0xfa, 0x7f,
+  0xe6, 0x3d, 0xed, 0xd4, 0x2b, 0xce, 0xb7, 0x15, 0x3f, 0xc0, 0x72, 0x03,
+  0x5f, 0x8b, 0xba, 0x16, 0x90, 0x25, 0xf7, 0xc2, 0x83, 0xd8, 0xc7, 0x75,
+  0x34, 0x63, 0x68, 0x12, 0x53, 0x0c, 0x53, 0x89, 0x7b, 0xc9, 0x56, 0x09,
+  0xa7, 0xc3, 0x36, 0x44, 0x4e, 0x0e, 0xd0, 0x62, 0x62, 0xb3, 0x86, 0xfa,
+  0xe8, 0xa1, 0x9b, 0x34, 0x67, 0x8d, 0x53, 0x22, 0x17, 0x3e, 0xfd, 0xac,
+  0xee, 0x67, 0x2e, 0x43, 0xe2, 0x5d, 0x7f, 0x33, 0x84, 0xf2, 0xa2, 0x70,
+  0xc0, 0x6e, 0x82, 0x97, 0xc0, 0x34, 0xfd, 0x25, 0xc6, 0x23, 0x7f, 0xed,
+  0xe6, 0xb0, 0xc5, 0x57, 0x43, 0x84, 0xb2, 0xde, 0x2d, 0xf1, 0xd0, 0xf6,
+  0x48, 0x1f, 0x14, 0x71, 0x57, 0xb2, 0xac, 0x31, 0xe1, 0x97, 0x24, 0x23,
+  0xc9, 0x13, 0x5d, 0x74, 0xe5, 0x46, 0xef, 0x09, 0x7c, 0x9e, 0xe1, 0x99,
+  0x31, 0x0a, 0x08, 0x79, 0x1b, 0x8f, 0x71, 0x9f, 0x17, 0x66, 0xc8, 0x38,
+  0xcf, 0xee, 0x8c, 0x97, 0xb6, 0x06, 0xb9, 0x73, 0x46, 0xe4, 0xd3, 0x94,
+  0xc1, 0xe5, 0x60, 0xb5, 0x25, 0x75, 0x2d, 0xd9, 0x69, 0x31, 0xec, 0xcd,
+  0x96, 0xc3, 0xa3, 0x76, 0xfd, 0xe8, 0x74, 0x44, 0xac, 0x12, 0xb9, 0x4d,
+  0xbf, 0x51, 0xe8, 0xb9, 0xd4, 0x44, 0x4e, 0x27, 0xcb, 0xae, 0x20, 0xd1,
+  0x7e, 0x2a, 0x7c, 0xb6, 0x63, 0x47, 0x9e, 0x76, 0xba, 0x97, 0xd0, 0x16,
+  0xe7, 0x0b, 0x6c, 0x6d, 0xf7, 0x43, 0x6f, 0x33, 0x0b, 0x29, 0x30, 0x77,
+  0xfa, 0x9d, 0xf9, 0xf5, 0x4e, 0xb8, 0x76, 0xb3, 0xcd, 0x18, 0xb4, 0xf9,
+  0x20, 0xef, 0x3d, 0xdb, 0xe6, 0xca, 0xad, 0x9b, 0xd0, 0x4e, 0xd2, 0x87,
+  0xa9, 0x0d, 0xa6, 0x44, 0x73, 0x50, 0xdd, 0x70, 0x5b, 0xed, 0xad, 0x7e,
+  0x4a, 0xbc, 0x22, 0xd5, 0xa8, 0x26, 0xe4, 0xc2, 0x85, 0x20, 0x0d, 0xd9,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            6e:cc:7a:a5:a7:03:20:09:b8:ce:bc:f4:e9:52:d4:91
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Feb  8 00:00:00 2010 GMT
+            Not After : Feb  7 23:59:59 2020 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Secure Server CA - G3
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b1:87:84:1f:c2:0c:45:f5:bc:ab:25:97:a7:ad:
+                    a2:3e:9c:ba:f6:c1:39:b8:8b:ca:c2:ac:56:c6:e5:
+                    bb:65:8e:44:4f:4d:ce:6f:ed:09:4a:d4:af:4e:10:
+                    9c:68:8b:2e:95:7b:89:9b:13:ca:e2:34:34:c1:f3:
+                    5b:f3:49:7b:62:83:48:81:74:d1:88:78:6c:02:53:
+                    f9:bc:7f:43:26:57:58:33:83:3b:33:0a:17:b0:d0:
+                    4e:91:24:ad:86:7d:64:12:dc:74:4a:34:a1:1d:0a:
+                    ea:96:1d:0b:15:fc:a3:4b:3b:ce:63:88:d0:f8:2d:
+                    0c:94:86:10:ca:b6:9a:3d:ca:eb:37:9c:00:48:35:
+                    86:29:50:78:e8:45:63:cd:19:41:4f:f5:95:ec:7b:
+                    98:d4:c4:71:b3:50:be:28:b3:8f:a0:b9:53:9c:f5:
+                    ca:2c:23:a9:fd:14:06:e8:18:b4:9a:e8:3c:6e:81:
+                    fd:e4:cd:35:36:b3:51:d3:69:ec:12:ba:56:6e:6f:
+                    9b:57:c5:8b:14:e7:0e:c7:9c:ed:4a:54:6a:c9:4d:
+                    c5:bf:11:b1:ae:1c:67:81:cb:44:55:33:99:7f:24:
+                    9b:3f:53:45:7f:86:1a:f3:3c:fa:6d:7f:81:f5:b8:
+                    4a:d3:f5:85:37:1c:b5:a6:d0:09:e4:18:7b:38:4e:
+                    fa:0f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.113733.1.7.23.3
+                  CPS: https://www.verisign.com/cps
+                  User Notice:
+                    Explicit Text: https://www.verisign.com/rpa
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3-g5.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-6
+            X509v3 Subject Key Identifier: 
+                0D:44:5C:16:53:44:C1:82:7E:1D:20:AB:25:F4:01:63:D8:BE:79:A5
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+    Signature Algorithm: sha1WithRSAEncryption
+         0c:83:24:ef:dd:c3:0c:d9:58:9c:fe:36:b6:eb:8a:80:4b:d1:
+         a3:f7:9d:f3:cc:53:ef:82:9e:a3:a1:e6:97:c1:58:9d:75:6c:
+         e0:1d:1b:4c:fa:d1:c1:2d:05:c0:ea:6e:b2:22:70:55:d9:20:
+         33:40:33:07:c2:65:83:fa:8f:43:37:9b:ea:0e:9a:6c:70:ee:
+         f6:9c:80:3b:d9:37:f4:7a:6d:ec:d0:18:7d:49:4a:ca:99:c7:
+         19:28:a2:be:d8:77:24:f7:85:26:86:6d:87:05:40:41:67:d1:
+         27:3a:ed:dc:48:1d:22:cd:0b:0b:8b:bc:f4:b1:7b:fd:b4:99:
+         a8:e9:76:2a:e1:1a:2d:87:6e:74:d3:88:dd:1e:22:c6:df:16:
+         b6:2b:82:14:0a:94:5c:f2:50:ec:af:ce:ff:62:37:0d:ad:65:
+         d3:06:41:53:ed:02:14:c8:b5:58:28:a1:ac:e0:5b:ec:b3:7f:
+         95:4a:fb:03:c8:ad:26:db:e6:66:78:12:4a:d9:9f:42:fb:e1:
+         98:e6:42:83:9b:8f:8f:67:24:e8:61:19:b5:dd:cd:b5:0b:26:
+         05:8e:c3:6e:c4:c8:75:b8:46:cf:e2:18:06:5e:a9:ae:a8:81:
+         9a:47:16:de:0c:28:6c:25:27:b9:de:b7:84:58:c6:1f:38:1e:
+         a4:c4:cb:66
+-----BEGIN CERTIFICATE-----
+MIIF7DCCBNSgAwIBAgIQbsx6pacDIAm4zrz06VLUkTANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtTEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMmVmVy
+aVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCxh4QfwgxF9byrJZenraI+nLr2wTm4i8rCrFbG
+5btljkRPTc5v7QlK1K9OEJxoiy6Ve4mbE8riNDTB81vzSXtig0iBdNGIeGwCU/m8
+f0MmV1gzgzszChew0E6RJK2GfWQS3HRKNKEdCuqWHQsV/KNLO85jiND4LQyUhhDK
+tpo9yus3nABINYYpUHjoRWPNGUFP9ZXse5jUxHGzUL4os4+guVOc9cosI6n9FAbo
+GLSa6Dxugf3kzTU2s1HTaewSulZub5tXxYsU5w7HnO1KVGrJTcW/EbGuHGeBy0RV
+M5l/JJs/U0V/hhrzPPptf4H1uErT9YU3HLWm0AnkGHs4TvoPAgMBAAGjggHfMIIB
+2zA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnZlcmlz
+aWduLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4
+RQEHFwMwVjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2Nw
+czAqBggrBgEFBQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMDQG
+A1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMtZzUu
+Y3JsMA4GA1UdDwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglp
+bWFnZS9naWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNo
+dHRwOi8vbG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjAoBgNVHREEITAfpB0w
+GzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItNjAdBgNVHQ4EFgQUDURcFlNEwYJ+
+HSCrJfQBY9i+eaUwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJ
+KoZIhvcNAQEFBQADggEBAAyDJO/dwwzZWJz+NrbrioBL0aP3nfPMU++CnqOh5pfB
+WJ11bOAdG0z60cEtBcDqbrIicFXZIDNAMwfCZYP6j0M3m+oOmmxw7vacgDvZN/R6
+bezQGH1JSsqZxxkoor7YdyT3hSaGbYcFQEFn0Sc67dxIHSLNCwuLvPSxe/20majp
+dirhGi2HbnTTiN0eIsbfFrYrghQKlFzyUOyvzv9iNw2tZdMGQVPtAhTItVgooazg
+W+yzf5VK+wPIrSbb5mZ4EkrZn0L74ZjmQoObj49nJOhhGbXdzbULJgWOw27EyHW4
+Rs/iGAZeqa6ogZpHFt4MKGwlJ7net4RYxh84HqTEy2Y=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert88[] = {
+  0x30, 0x82, 0x05, 0xec, 0x30, 0x82, 0x04, 0xd4, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x6e, 0xcc, 0x7a, 0xa5, 0xa7, 0x03, 0x20, 0x09, 0xb8,
+  0xce, 0xbc, 0xf4, 0xe9, 0x52, 0xd4, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xb5, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+  0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+  0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x2f,
+  0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72,
+  0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30,
+  0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+  0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0x87, 0x84, 0x1f,
+  0xc2, 0x0c, 0x45, 0xf5, 0xbc, 0xab, 0x25, 0x97, 0xa7, 0xad, 0xa2, 0x3e,
+  0x9c, 0xba, 0xf6, 0xc1, 0x39, 0xb8, 0x8b, 0xca, 0xc2, 0xac, 0x56, 0xc6,
+  0xe5, 0xbb, 0x65, 0x8e, 0x44, 0x4f, 0x4d, 0xce, 0x6f, 0xed, 0x09, 0x4a,
+  0xd4, 0xaf, 0x4e, 0x10, 0x9c, 0x68, 0x8b, 0x2e, 0x95, 0x7b, 0x89, 0x9b,
+  0x13, 0xca, 0xe2, 0x34, 0x34, 0xc1, 0xf3, 0x5b, 0xf3, 0x49, 0x7b, 0x62,
+  0x83, 0x48, 0x81, 0x74, 0xd1, 0x88, 0x78, 0x6c, 0x02, 0x53, 0xf9, 0xbc,
+  0x7f, 0x43, 0x26, 0x57, 0x58, 0x33, 0x83, 0x3b, 0x33, 0x0a, 0x17, 0xb0,
+  0xd0, 0x4e, 0x91, 0x24, 0xad, 0x86, 0x7d, 0x64, 0x12, 0xdc, 0x74, 0x4a,
+  0x34, 0xa1, 0x1d, 0x0a, 0xea, 0x96, 0x1d, 0x0b, 0x15, 0xfc, 0xa3, 0x4b,
+  0x3b, 0xce, 0x63, 0x88, 0xd0, 0xf8, 0x2d, 0x0c, 0x94, 0x86, 0x10, 0xca,
+  0xb6, 0x9a, 0x3d, 0xca, 0xeb, 0x37, 0x9c, 0x00, 0x48, 0x35, 0x86, 0x29,
+  0x50, 0x78, 0xe8, 0x45, 0x63, 0xcd, 0x19, 0x41, 0x4f, 0xf5, 0x95, 0xec,
+  0x7b, 0x98, 0xd4, 0xc4, 0x71, 0xb3, 0x50, 0xbe, 0x28, 0xb3, 0x8f, 0xa0,
+  0xb9, 0x53, 0x9c, 0xf5, 0xca, 0x2c, 0x23, 0xa9, 0xfd, 0x14, 0x06, 0xe8,
+  0x18, 0xb4, 0x9a, 0xe8, 0x3c, 0x6e, 0x81, 0xfd, 0xe4, 0xcd, 0x35, 0x36,
+  0xb3, 0x51, 0xd3, 0x69, 0xec, 0x12, 0xba, 0x56, 0x6e, 0x6f, 0x9b, 0x57,
+  0xc5, 0x8b, 0x14, 0xe7, 0x0e, 0xc7, 0x9c, 0xed, 0x4a, 0x54, 0x6a, 0xc9,
+  0x4d, 0xc5, 0xbf, 0x11, 0xb1, 0xae, 0x1c, 0x67, 0x81, 0xcb, 0x44, 0x55,
+  0x33, 0x99, 0x7f, 0x24, 0x9b, 0x3f, 0x53, 0x45, 0x7f, 0x86, 0x1a, 0xf3,
+  0x3c, 0xfa, 0x6d, 0x7f, 0x81, 0xf5, 0xb8, 0x4a, 0xd3, 0xf5, 0x85, 0x37,
+  0x1c, 0xb5, 0xa6, 0xd0, 0x09, 0xe4, 0x18, 0x7b, 0x38, 0x4e, 0xfa, 0x0f,
+  0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xdf, 0x30, 0x82, 0x01,
+  0xdb, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+  0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55,
+  0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff,
+  0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x69,
+  0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+  0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+  0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+  0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+  0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+  0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+  0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x34, 0x06,
+  0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27,
+  0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e,
+  0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+  0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b,
+  0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1,
+  0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69,
+  0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f,
+  0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f,
+  0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a,
+  0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76,
+  0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x28,
+  0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30,
+  0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49,
+  0x2d, 0x32, 0x2d, 0x36, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+  0x16, 0x04, 0x14, 0x0d, 0x44, 0x5c, 0x16, 0x53, 0x44, 0xc1, 0x82, 0x7e,
+  0x1d, 0x20, 0xab, 0x25, 0xf4, 0x01, 0x63, 0xd8, 0xbe, 0x79, 0xa5, 0x30,
+  0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+  0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+  0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x0c, 0x83, 0x24, 0xef, 0xdd, 0xc3, 0x0c, 0xd9,
+  0x58, 0x9c, 0xfe, 0x36, 0xb6, 0xeb, 0x8a, 0x80, 0x4b, 0xd1, 0xa3, 0xf7,
+  0x9d, 0xf3, 0xcc, 0x53, 0xef, 0x82, 0x9e, 0xa3, 0xa1, 0xe6, 0x97, 0xc1,
+  0x58, 0x9d, 0x75, 0x6c, 0xe0, 0x1d, 0x1b, 0x4c, 0xfa, 0xd1, 0xc1, 0x2d,
+  0x05, 0xc0, 0xea, 0x6e, 0xb2, 0x22, 0x70, 0x55, 0xd9, 0x20, 0x33, 0x40,
+  0x33, 0x07, 0xc2, 0x65, 0x83, 0xfa, 0x8f, 0x43, 0x37, 0x9b, 0xea, 0x0e,
+  0x9a, 0x6c, 0x70, 0xee, 0xf6, 0x9c, 0x80, 0x3b, 0xd9, 0x37, 0xf4, 0x7a,
+  0x6d, 0xec, 0xd0, 0x18, 0x7d, 0x49, 0x4a, 0xca, 0x99, 0xc7, 0x19, 0x28,
+  0xa2, 0xbe, 0xd8, 0x77, 0x24, 0xf7, 0x85, 0x26, 0x86, 0x6d, 0x87, 0x05,
+  0x40, 0x41, 0x67, 0xd1, 0x27, 0x3a, 0xed, 0xdc, 0x48, 0x1d, 0x22, 0xcd,
+  0x0b, 0x0b, 0x8b, 0xbc, 0xf4, 0xb1, 0x7b, 0xfd, 0xb4, 0x99, 0xa8, 0xe9,
+  0x76, 0x2a, 0xe1, 0x1a, 0x2d, 0x87, 0x6e, 0x74, 0xd3, 0x88, 0xdd, 0x1e,
+  0x22, 0xc6, 0xdf, 0x16, 0xb6, 0x2b, 0x82, 0x14, 0x0a, 0x94, 0x5c, 0xf2,
+  0x50, 0xec, 0xaf, 0xce, 0xff, 0x62, 0x37, 0x0d, 0xad, 0x65, 0xd3, 0x06,
+  0x41, 0x53, 0xed, 0x02, 0x14, 0xc8, 0xb5, 0x58, 0x28, 0xa1, 0xac, 0xe0,
+  0x5b, 0xec, 0xb3, 0x7f, 0x95, 0x4a, 0xfb, 0x03, 0xc8, 0xad, 0x26, 0xdb,
+  0xe6, 0x66, 0x78, 0x12, 0x4a, 0xd9, 0x9f, 0x42, 0xfb, 0xe1, 0x98, 0xe6,
+  0x42, 0x83, 0x9b, 0x8f, 0x8f, 0x67, 0x24, 0xe8, 0x61, 0x19, 0xb5, 0xdd,
+  0xcd, 0xb5, 0x0b, 0x26, 0x05, 0x8e, 0xc3, 0x6e, 0xc4, 0xc8, 0x75, 0xb8,
+  0x46, 0xcf, 0xe2, 0x18, 0x06, 0x5e, 0xa9, 0xae, 0xa8, 0x81, 0x9a, 0x47,
+  0x16, 0xde, 0x0c, 0x28, 0x6c, 0x25, 0x27, 0xb9, 0xde, 0xb7, 0x84, 0x58,
+  0xc6, 0x1f, 0x38, 0x1e, 0xa4, 0xc4, 0xcb, 0x66,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            71:63:66:35:eb:f3:82:3d:7e:13:09:59:a2:d8:e5:de
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G3
+        Validity
+            Not Before: Oct 12 00:00:00 2010 GMT
+            Not After : Oct 11 23:59:59 2020 GMT
+        Subject: C=US, O=Oracle Corporation, OU=VeriSign Trust Network, OU=Class 3 MPKI Secure Server CA, CN=Oracle SSL CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:a4:3e:5a:70:29:b8:f1:9f:22:b9:21:8e:c5:52:
+                    ac:32:aa:bf:1a:1b:29:f3:3d:79:ea:6a:1c:29:48:
+                    f4:6f:fe:86:31:23:3d:07:23:17:05:3b:5e:04:fd:
+                    3e:5c:1a:6d:63:61:64:90:49:67:de:69:8f:f4:72:
+                    a7:23:87:1a:66:98:da:07:3c:21:2c:ac:86:8d:11:
+                    7d:40:98:40:26:59:a9:c5:af:aa:f2:e9:d7:11:91:
+                    9b:f1:d6:6f:cd:65:63:3d:8f:76:a1:99:2f:c6:3f:
+                    9d:fa:57:82:b1:ff:11:0b:c4:ec:84:d2:d4:47:ef:
+                    2c:bf:90:eb:61:95:ee:eb:17:c0:43:d6:83:67:7b:
+                    54:80:f4:0d:06:9f:0a:ed:d9:de:5c:66:fd:49:a6:
+                    e8:3f:96:3c:fa:c9:46:96:65:af:82:73:26:e0:94:
+                    0b:bd:99:c0:b5:61:a6:ec:dc:be:57:d4:57:91:ca:
+                    18:0e:2c:cc:0c:8a:e0:a4:7c:a3:e5:7c:0c:3e:97:
+                    df:62:b9:80:a5:11:35:db:6b:fb:91:45:3c:2f:48:
+                    e9:58:05:6d:8e:cd:04:72:2e:04:a2:ae:18:66:79:
+                    e9:38:f7:78:ec:62:af:eb:a6:f8:c5:4a:7c:58:85:
+                    60:7d:20:6a:7d:84:c6:32:3a:66:ea:33:ec:e8:4d:
+                    93:f1
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:1
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.113733.1.7.23.3
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3-g3.crl
+
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-20
+            X509v3 Subject Key Identifier: 
+                CC:F8:BB:65:47:6A:52:16:C4:EC:7E:9B:27:9C:FC:2E:A9:C2:F0:0F
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3
+                serial:9B:7E:06:49:A3:3E:62:B9:D5:EE:90:48:71:29:EF:57
+
+    Signature Algorithm: sha1WithRSAEncryption
+         47:8b:0e:12:54:a1:ce:ca:59:ad:c1:81:65:66:2f:8f:5b:71:
+         c5:86:a3:90:a3:7b:e0:7c:f1:60:1a:81:87:7f:df:c1:7e:9c:
+         a0:5b:d4:db:c0:bc:ac:78:4d:51:59:9d:28:24:db:46:a1:74:
+         e2:d7:2e:2b:7f:74:09:03:d3:aa:31:f1:47:fb:b5:a3:e5:5e:
+         40:d1:b6:a3:c5:e5:cf:5f:26:7b:ab:17:ab:91:8a:2f:f9:d8:
+         0a:34:54:f6:6a:63:52:9b:d7:70:8b:34:46:14:2a:7b:09:40:
+         04:a0:1a:6f:d3:4b:2a:f5:12:9c:22:db:3b:ce:b2:7e:15:f0:
+         f3:4e:3e:e4:7f:b6:8a:bf:68:04:b5:9c:5d:bb:8f:a5:c8:29:
+         95:5f:5b:c6:e4:df:84:9c:80:74:1a:35:1d:fd:94:ac:86:85:
+         89:2e:90:7f:59:b6:9c:06:e5:35:ff:ff:ff:b5:53:d9:3e:b5:
+         dd:ae:fe:06:4f:66:71:e0:4f:f7:fc:c1:85:b5:7b:85:43:22:
+         cf:5b:f6:94:85:a6:59:b2:5d:fe:29:4f:8c:9c:1e:92:ce:0f:
+         33:20:19:49:59:54:36:6c:c4:e9:f9:66:1b:20:6c:b2:6f:3e:
+         24:39:6f:91:fb:b4:d8:93:50:c0:c2:97:de:e9:93:5e:97:20:
+         05:4a:09:13
+-----BEGIN CERTIFICATE-----
+MIIF+zCCBOOgAwIBAgIQcWNmNevzgj1+EwlZotjl3jANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzMwHhcNMTAxMDEyMDAwMDAwWhcNMjAxMDExMjM1OTU5WjCBizEL
+MAkGA1UEBhMCVVMxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3JhdGlvbjEfMB0GA1UE
+CxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEmMCQGA1UECxMdQ2xhc3MgMyBNUEtJ
+IFNlY3VyZSBTZXJ2ZXIgQ0ExFjAUBgNVBAMTDU9yYWNsZSBTU0wgQ0EwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkPlpwKbjxnyK5IY7FUqwyqr8aGynz
+PXnqahwpSPRv/oYxIz0HIxcFO14E/T5cGm1jYWSQSWfeaY/0cqcjhxpmmNoHPCEs
+rIaNEX1AmEAmWanFr6ry6dcRkZvx1m/NZWM9j3ahmS/GP536V4Kx/xELxOyE0tRH
+7yy/kOthle7rF8BD1oNne1SA9A0Gnwrt2d5cZv1Jpug/ljz6yUaWZa+CcybglAu9
+mcC1Yabs3L5X1FeRyhgOLMwMiuCkfKPlfAw+l99iuYClETXba/uRRTwvSOlYBW2O
+zQRyLgSirhhmeek493jsYq/rpvjFSnxYhWB9IGp9hMYyOmbqM+zoTZPxAgMBAAGj
+ggIYMIICFDAOBgNVHQ8BAf8EBAMCAQYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF
+BzABhhhodHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wEgYDVR0TAQH/BAgwBgEB/wIB
+ATBEBgNVHSAEPTA7MDkGC2CGSAGG+EUBBxcDMCowKAYIKwYBBQUHAgEWHGh0dHBz
+Oi8vd3d3LnZlcmlzaWduLmNvbS9jcHMwNAYDVR0fBC0wKzApoCegJYYjaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy1nMy5jcmwwKQYDVR0RBCIwIKQeMBwxGjAY
+BgNVBAMTEVZlcmlTaWduTVBLSS0yLTIwMB0GA1UdDgQWBBTM+LtlR2pSFsTsfpsn
+nPwuqcLwDzCB8QYDVR0jBIHpMIHmoYHQpIHNMIHKMQswCQYDVQQGEwJVUzEXMBUG
+A1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5l
+dHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1
+dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHM4IRAJt+Bkmj
+PmK51e6QSHEp71cwDQYJKoZIhvcNAQEFBQADggEBAEeLDhJUoc7KWa3BgWVmL49b
+ccWGo5Cje+B88WAagYd/38F+nKBb1NvAvKx4TVFZnSgk20ahdOLXLit/dAkD06ox
+8Uf7taPlXkDRtqPF5c9fJnurF6uRii/52Ao0VPZqY1Kb13CLNEYUKnsJQASgGm/T
+Syr1Epwi2zvOsn4V8PNOPuR/toq/aAS1nF27j6XIKZVfW8bk34ScgHQaNR39lKyG
+hYkukH9ZtpwG5TX///+1U9k+td2u/gZPZnHgT/f8wYW1e4VDIs9b9pSFplmyXf4p
+T4ycHpLODzMgGUlZVDZsxOn5ZhsgbLJvPiQ5b5H7tNiTUMDCl97pk16XIAVKCRM=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert89[] = {
+  0x30, 0x82, 0x05, 0xfb, 0x30, 0x82, 0x04, 0xe3, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x71, 0x63, 0x66, 0x35, 0xeb, 0xf3, 0x82, 0x3d, 0x7e,
+  0x13, 0x09, 0x59, 0xa2, 0xd8, 0xe5, 0xde, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x30, 0x31, 0x32, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x31, 0x30, 0x31, 0x31,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x12, 0x4f, 0x72,
+  0x61, 0x63, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20,
+  0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+  0x6b, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d,
+  0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x4d, 0x50, 0x4b, 0x49,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76,
+  0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x0d, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x20, 0x53,
+  0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+  0x01, 0x00, 0xa4, 0x3e, 0x5a, 0x70, 0x29, 0xb8, 0xf1, 0x9f, 0x22, 0xb9,
+  0x21, 0x8e, 0xc5, 0x52, 0xac, 0x32, 0xaa, 0xbf, 0x1a, 0x1b, 0x29, 0xf3,
+  0x3d, 0x79, 0xea, 0x6a, 0x1c, 0x29, 0x48, 0xf4, 0x6f, 0xfe, 0x86, 0x31,
+  0x23, 0x3d, 0x07, 0x23, 0x17, 0x05, 0x3b, 0x5e, 0x04, 0xfd, 0x3e, 0x5c,
+  0x1a, 0x6d, 0x63, 0x61, 0x64, 0x90, 0x49, 0x67, 0xde, 0x69, 0x8f, 0xf4,
+  0x72, 0xa7, 0x23, 0x87, 0x1a, 0x66, 0x98, 0xda, 0x07, 0x3c, 0x21, 0x2c,
+  0xac, 0x86, 0x8d, 0x11, 0x7d, 0x40, 0x98, 0x40, 0x26, 0x59, 0xa9, 0xc5,
+  0xaf, 0xaa, 0xf2, 0xe9, 0xd7, 0x11, 0x91, 0x9b, 0xf1, 0xd6, 0x6f, 0xcd,
+  0x65, 0x63, 0x3d, 0x8f, 0x76, 0xa1, 0x99, 0x2f, 0xc6, 0x3f, 0x9d, 0xfa,
+  0x57, 0x82, 0xb1, 0xff, 0x11, 0x0b, 0xc4, 0xec, 0x84, 0xd2, 0xd4, 0x47,
+  0xef, 0x2c, 0xbf, 0x90, 0xeb, 0x61, 0x95, 0xee, 0xeb, 0x17, 0xc0, 0x43,
+  0xd6, 0x83, 0x67, 0x7b, 0x54, 0x80, 0xf4, 0x0d, 0x06, 0x9f, 0x0a, 0xed,
+  0xd9, 0xde, 0x5c, 0x66, 0xfd, 0x49, 0xa6, 0xe8, 0x3f, 0x96, 0x3c, 0xfa,
+  0xc9, 0x46, 0x96, 0x65, 0xaf, 0x82, 0x73, 0x26, 0xe0, 0x94, 0x0b, 0xbd,
+  0x99, 0xc0, 0xb5, 0x61, 0xa6, 0xec, 0xdc, 0xbe, 0x57, 0xd4, 0x57, 0x91,
+  0xca, 0x18, 0x0e, 0x2c, 0xcc, 0x0c, 0x8a, 0xe0, 0xa4, 0x7c, 0xa3, 0xe5,
+  0x7c, 0x0c, 0x3e, 0x97, 0xdf, 0x62, 0xb9, 0x80, 0xa5, 0x11, 0x35, 0xdb,
+  0x6b, 0xfb, 0x91, 0x45, 0x3c, 0x2f, 0x48, 0xe9, 0x58, 0x05, 0x6d, 0x8e,
+  0xcd, 0x04, 0x72, 0x2e, 0x04, 0xa2, 0xae, 0x18, 0x66, 0x79, 0xe9, 0x38,
+  0xf7, 0x78, 0xec, 0x62, 0xaf, 0xeb, 0xa6, 0xf8, 0xc5, 0x4a, 0x7c, 0x58,
+  0x85, 0x60, 0x7d, 0x20, 0x6a, 0x7d, 0x84, 0xc6, 0x32, 0x3a, 0x66, 0xea,
+  0x33, 0xec, 0xe8, 0x4d, 0x93, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+  0x82, 0x02, 0x18, 0x30, 0x82, 0x02, 0x14, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3d, 0x30, 0x3b,
+  0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+  0x07, 0x17, 0x03, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73,
+  0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30,
+  0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29,
+  0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67,
+  0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11,
+  0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18,
+  0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65, 0x72, 0x69, 0x53,
+  0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x32, 0x30,
+  0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcc,
+  0xf8, 0xbb, 0x65, 0x47, 0x6a, 0x52, 0x16, 0xc4, 0xec, 0x7e, 0x9b, 0x27,
+  0x9c, 0xfc, 0x2e, 0xa9, 0xc2, 0xf0, 0x0f, 0x30, 0x81, 0xf1, 0x06, 0x03,
+  0x55, 0x1d, 0x23, 0x04, 0x81, 0xe9, 0x30, 0x81, 0xe6, 0xa1, 0x81, 0xd0,
+  0xa4, 0x81, 0xcd, 0x30, 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+  0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06,
+  0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69,
+  0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53,
+  0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65,
+  0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+  0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55,
+  0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+  0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+  0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+  0x2d, 0x20, 0x47, 0x33, 0x82, 0x11, 0x00, 0x9b, 0x7e, 0x06, 0x49, 0xa3,
+  0x3e, 0x62, 0xb9, 0xd5, 0xee, 0x90, 0x48, 0x71, 0x29, 0xef, 0x57, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x47, 0x8b, 0x0e, 0x12, 0x54,
+  0xa1, 0xce, 0xca, 0x59, 0xad, 0xc1, 0x81, 0x65, 0x66, 0x2f, 0x8f, 0x5b,
+  0x71, 0xc5, 0x86, 0xa3, 0x90, 0xa3, 0x7b, 0xe0, 0x7c, 0xf1, 0x60, 0x1a,
+  0x81, 0x87, 0x7f, 0xdf, 0xc1, 0x7e, 0x9c, 0xa0, 0x5b, 0xd4, 0xdb, 0xc0,
+  0xbc, 0xac, 0x78, 0x4d, 0x51, 0x59, 0x9d, 0x28, 0x24, 0xdb, 0x46, 0xa1,
+  0x74, 0xe2, 0xd7, 0x2e, 0x2b, 0x7f, 0x74, 0x09, 0x03, 0xd3, 0xaa, 0x31,
+  0xf1, 0x47, 0xfb, 0xb5, 0xa3, 0xe5, 0x5e, 0x40, 0xd1, 0xb6, 0xa3, 0xc5,
+  0xe5, 0xcf, 0x5f, 0x26, 0x7b, 0xab, 0x17, 0xab, 0x91, 0x8a, 0x2f, 0xf9,
+  0xd8, 0x0a, 0x34, 0x54, 0xf6, 0x6a, 0x63, 0x52, 0x9b, 0xd7, 0x70, 0x8b,
+  0x34, 0x46, 0x14, 0x2a, 0x7b, 0x09, 0x40, 0x04, 0xa0, 0x1a, 0x6f, 0xd3,
+  0x4b, 0x2a, 0xf5, 0x12, 0x9c, 0x22, 0xdb, 0x3b, 0xce, 0xb2, 0x7e, 0x15,
+  0xf0, 0xf3, 0x4e, 0x3e, 0xe4, 0x7f, 0xb6, 0x8a, 0xbf, 0x68, 0x04, 0xb5,
+  0x9c, 0x5d, 0xbb, 0x8f, 0xa5, 0xc8, 0x29, 0x95, 0x5f, 0x5b, 0xc6, 0xe4,
+  0xdf, 0x84, 0x9c, 0x80, 0x74, 0x1a, 0x35, 0x1d, 0xfd, 0x94, 0xac, 0x86,
+  0x85, 0x89, 0x2e, 0x90, 0x7f, 0x59, 0xb6, 0x9c, 0x06, 0xe5, 0x35, 0xff,
+  0xff, 0xff, 0xb5, 0x53, 0xd9, 0x3e, 0xb5, 0xdd, 0xae, 0xfe, 0x06, 0x4f,
+  0x66, 0x71, 0xe0, 0x4f, 0xf7, 0xfc, 0xc1, 0x85, 0xb5, 0x7b, 0x85, 0x43,
+  0x22, 0xcf, 0x5b, 0xf6, 0x94, 0x85, 0xa6, 0x59, 0xb2, 0x5d, 0xfe, 0x29,
+  0x4f, 0x8c, 0x9c, 0x1e, 0x92, 0xce, 0x0f, 0x33, 0x20, 0x19, 0x49, 0x59,
+  0x54, 0x36, 0x6c, 0xc4, 0xe9, 0xf9, 0x66, 0x1b, 0x20, 0x6c, 0xb2, 0x6f,
+  0x3e, 0x24, 0x39, 0x6f, 0x91, 0xfb, 0xb4, 0xd8, 0x93, 0x50, 0xc0, 0xc2,
+  0x97, 0xde, 0xe9, 0x93, 0x5e, 0x97, 0x20, 0x05, 0x4a, 0x09, 0x13,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            61:5d:aa:d2:00:06:00:00:00:40
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: CN=Microsoft Internet Authority
+        Validity
+            Not Before: May 15 20:40:55 2012 GMT
+            Not After : May 15 20:50:55 2016 GMT
+        Subject: DC=com, DC=microsoft, DC=corp, DC=redmond, CN=MSIT Machine Auth CA 2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bd:c8:e8:00:eb:58:69:29:11:84:87:1c:9f:87:
+                    4d:44:3d:38:1b:c7:13:93:e7:14:4c:17:2b:db:75:
+                    08:c0:c9:21:ca:ae:e0:1f:e8:8c:fe:a1:df:24:8c:
+                    bf:02:9c:ed:99:be:3a:53:2a:45:4e:b0:48:78:37:
+                    dc:a1:63:ef:03:b7:94:29:66:6c:66:d7:6c:6a:48:
+                    65:b2:dd:47:21:23:1b:b8:41:74:2f:96:dd:98:22:
+                    b9:fa:f3:0e:4a:b0:2f:0b:a2:de:b7:02:13:42:70:
+                    3f:78:04:14:72:e3:3a:2b:7e:28:48:1d:96:b4:db:
+                    16:39:8d:b3:c4:59:a1:d7:a2:d2:64:61:33:2b:41:
+                    18:c5:ab:95:6f:5d:22:07:77:cd:53:9d:03:49:65:
+                    d5:88:f5:5f:9d:f0:c4:69:4f:91:08:a5:39:07:96:
+                    36:af:2d:64:dc:26:5a:c1:13:ee:31:39:d6:5f:dc:
+                    97:fc:27:aa:05:78:47:c5:22:26:63:53:7e:37:c2:
+                    7b:64:3d:69:cb:f0:fb:8a:15:3e:52:b3:86:6a:b4:
+                    c1:1c:3b:b2:f5:c7:3e:c5:76:dd:74:68:76:7a:55:
+                    e6:80:7b:2e:8c:a6:da:bb:91:5a:07:cd:19:4a:ea:
+                    08:5c:ff:c1:49:d6:7b:06:bf:eb:b7:4a:9f:b4:27:
+                    9c:17
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            1.3.6.1.4.1.311.21.1: 
+                .....
+            1.3.6.1.4.1.311.21.2: 
+                ..#...h..f...@z.g.3...
+            X509v3 Subject Key Identifier: 
+                EB:DB:11:5E:F8:09:9E:D8:D6:62:9C:FD:62:9D:E3:84:4A:28:E1:27
+            1.3.6.1.4.1.311.20.2: 
+                .
+.S.u.b.C.A
+            X509v3 Key Usage: 
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Authority Key Identifier: 
+                keyid:2A:4D:97:95:5D:34:7E:9D:B6:E6:33:BE:9C:27:C1:70:7E:67:DB:C1
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://mscrl.microsoft.com/pki/mscorp/crl/mswww(6).crl
+                  URI:http://crl.microsoft.com/pki/mscorp/crl/mswww(6).crl
+                  URI:http://corppki/crl/mswww(6).crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://www.microsoft.com/pki/mscorp/mswww(6).crt
+                CA Issuers - URI:http://corppki/aia/mswww(6).crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         a3:36:72:f7:45:0b:a7:86:37:de:20:8d:f5:d7:8e:da:89:00:
+         7a:52:4b:85:32:b2:32:d7:ed:80:57:fd:0d:51:1c:d1:1e:3d:
+         0c:3e:91:2a:30:e2:53:bc:61:07:89:02:11:b9:1b:83:48:d3:
+         1a:c7:47:96:62:b7:cf:de:d7:0d:b8:9d:84:8e:de:d5:e6:e4:
+         b9:c0:06:e3:1c:f4:31:43:1f:53:fc:4a:42:b5:9b:23:cd:b2:
+         ec:0d:0f:81:89:ff:59:a9:00:d3:04:4a:0b:af:5b:84:f5:3b:
+         4f:b9:37:91:88:21:e4:9c:ca:52:76:63:7e:88:7a:65:b4:8e:
+         16:6a:f4:60:bd:2c:0e:ef:17:86:2b:75:09:58:73:c5:4f:b9:
+         a8:1b:ef:2a:f4:b6:a3:b9:07:f0:a4:90:39:53:df:e1:ba:02:
+         98:a5:a5:82:11:a1:06:53:b2:c2:eb:fd:ea:67:72:37:63:d0:
+         85:cf:86:05:c8:c3:73:c1:db:f3:c4:d2:55:ff:a0:1d:e7:72:
+         14:1a:ff:b2:ff:08:42:48:40:0a:19:82:cf:22:f3:05:c5:d5:
+         df:0b:29:c4:3a:0e:c5:32:38:ef:2f:94:9a:0c:f2:d4:ee:bf:
+         62:c7:e2:a4:5f:3d:6e:78:bc:10:c1:2e:4a:30:f2:87:db:89:
+         38:be:fe:cc:80:8f:f5:f9:5c:cb:5c:f1:ea:02:08:6b:a5:0b:
+         ef:20:5c:a3:34:cc:f0:80:b6:1f:63:b9:44:32:1c:26:41:9b:
+         dd:93:a3:64:01:57:11:21:43:94:2a:57:2d:8a:4a:cb:8a:28:
+         40:0a:fb:50:f9:af:26:74:13:97:82:4e:6c:64:eb:d1:c6:bf:
+         5e:fd:25:da:05:46:4a:ae:c7:2f:c7:04:ef:2e:71:2e:e2:a8:
+         5b:a4:ea:b2:6f:a8:91:35:c4:b7:63:17:62:0e:27:8e:3c:24:
+         cd:3d:45:69:05:dc:52:c5:35:f8:11:c0:1d:df:62:60:f4:e1:
+         a6:5e:70:b8:45:74:03:ab:d1:16:74:e3:9e:d3:c1:a3:e8:90:
+         96:8a:8a:c2:46:46:9d:b9:5c:6c:02:1d:32:84:eb:14:85:76:
+         95:aa:4d:2d:69:b6:02:f6:fe:ed:34:d5:8c:e6:fa:ac:5d:dc:
+         03:40:e6:cf:77:89:ff:b1:28:ca:86:8c:c8:e7:31:47:fc:16:
+         fe:54:0c:f5:26:b1:7e:dc:98:26:70:58:26:13:5c:c7:75:db:
+         12:de:4c:ac:ff:9a:0c:ea:a2:c2:1c:41:04:8c:e6:47:97:47:
+         6f:89:c5:48:de:37:0d:6a:d9:f0:68:24:5c:ff:19:59:e6:e1:
+         70:37:38:0d:db:ee:b0:e2
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIKYV2q0gAGAAAAQDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
+VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTEyMDUxNTIwNDA1
+NVoXDTE2MDUxNTIwNTA1NVowgYAxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
+kiaJk/IsZAEZFgltaWNyb3NvZnQxFDASBgoJkiaJk/IsZAEZFgRjb3JwMRcwFQYK
+CZImiZPyLGQBGRYHcmVkbW9uZDEfMB0GA1UEAxMWTVNJVCBNYWNoaW5lIEF1dGgg
+Q0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3I6ADrWGkpEYSH
+HJ+HTUQ9OBvHE5PnFEwXK9t1CMDJIcqu4B/ojP6h3ySMvwKc7Zm+OlMqRU6wSHg3
+3KFj7wO3lClmbGbXbGpIZbLdRyEjG7hBdC+W3ZgiufrzDkqwLwui3rcCE0JwP3gE
+FHLjOit+KEgdlrTbFjmNs8RZodei0mRhMytBGMWrlW9dIgd3zVOdA0ll1Yj1X53w
+xGlPkQilOQeWNq8tZNwmWsET7jE51l/cl/wnqgV4R8UiJmNTfjfCe2Q9acvw+4oV
+PlKzhmq0wRw7svXHPsV23XRodnpV5oB7Loym2ruRWgfNGUrqCFz/wUnWewa/67dK
+n7QnnBcCAwEAAaOCAdowggHWMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGC
+NxUCBBYEFCO30O1oke9mm+EFQHq+Z8oz1ga9MB0GA1UdDgQWBBTr2xFe+Ame2NZi
+nP1ineOESijhJzAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
+AYYwEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBQqTZeVXTR+nbbmM76c
+J8FwfmfbwTCBowYDVR0fBIGbMIGYMIGVoIGSoIGPhjZodHRwOi8vbXNjcmwubWlj
+cm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9tc3d3dyg2KS5jcmyGNGh0dHA6Ly9j
+cmwubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9tc3d3dyg2KS5jcmyGH2h0
+dHA6Ly9jb3JwcGtpL2NybC9tc3d3dyg2KS5jcmwweQYIKwYBBQUHAQEEbTBrMDwG
+CCsGAQUFBzAChjBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9t
+c3d3dyg2KS5jcnQwKwYIKwYBBQUHMAKGH2h0dHA6Ly9jb3JwcGtpL2FpYS9tc3d3
+dyg2KS5jcnQwDQYJKoZIhvcNAQEFBQADggIBAKM2cvdFC6eGN94gjfXXjtqJAHpS
+S4UysjLX7YBX/Q1RHNEePQw+kSow4lO8YQeJAhG5G4NI0xrHR5Zit8/e1w24nYSO
+3tXm5LnABuMc9DFDH1P8SkK1myPNsuwND4GJ/1mpANMESguvW4T1O0+5N5GIIeSc
+ylJ2Y36IemW0jhZq9GC9LA7vF4YrdQlYc8VPuagb7yr0tqO5B/CkkDlT3+G6Apil
+pYIRoQZTssLr/epncjdj0IXPhgXIw3PB2/PE0lX/oB3nchQa/7L/CEJIQAoZgs8i
+8wXF1d8LKcQ6DsUyOO8vlJoM8tTuv2LH4qRfPW54vBDBLkow8ofbiTi+/syAj/X5
+XMtc8eoCCGulC+8gXKM0zPCAth9juUQyHCZBm92To2QBVxEhQ5QqVy2KSsuKKEAK
++1D5ryZ0E5eCTmxk69HGv179JdoFRkquxy/HBO8ucS7iqFuk6rJvqJE1xLdjF2IO
+J448JM09RWkF3FLFNfgRwB3fYmD04aZecLhFdAOr0RZ0457TwaPokJaKisJGRp25
+XGwCHTKE6xSFdpWqTS1ptgL2/u001Yzm+qxd3ANA5s93if+xKMqGjMjnMUf8Fv5U
+DPUmsX7cmCZwWCYTXMd12xLeTKz/mgzqosIcQQSM5keXR2+JxUjeNw1q2fBoJFz/
+GVnm4XA3OA3b7rDi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert90[] = {
+  0x30, 0x82, 0x06, 0x08, 0x30, 0x82, 0x03, 0xf0, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0a, 0x61, 0x5d, 0xaa, 0xd2, 0x00, 0x06, 0x00, 0x00, 0x00,
+  0x40, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+  0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x32, 0x30, 0x35, 0x31, 0x35, 0x32, 0x30, 0x34, 0x30, 0x35,
+  0x35, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x35, 0x31, 0x35, 0x32, 0x30,
+  0x35, 0x30, 0x35, 0x35, 0x5a, 0x30, 0x81, 0x80, 0x31, 0x13, 0x30, 0x11,
+  0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+  0x16, 0x03, 0x63, 0x6f, 0x6d, 0x31, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x09,
+  0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x09, 0x6d,
+  0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x31, 0x14, 0x30, 0x12,
+  0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+  0x16, 0x04, 0x63, 0x6f, 0x72, 0x70, 0x31, 0x17, 0x30, 0x15, 0x06, 0x0a,
+  0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x07,
+  0x72, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1f, 0x30, 0x1d, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x4d, 0x53, 0x49, 0x54, 0x20, 0x4d,
+  0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20,
+  0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xbd, 0xc8, 0xe8, 0x00, 0xeb, 0x58, 0x69, 0x29, 0x11, 0x84, 0x87,
+  0x1c, 0x9f, 0x87, 0x4d, 0x44, 0x3d, 0x38, 0x1b, 0xc7, 0x13, 0x93, 0xe7,
+  0x14, 0x4c, 0x17, 0x2b, 0xdb, 0x75, 0x08, 0xc0, 0xc9, 0x21, 0xca, 0xae,
+  0xe0, 0x1f, 0xe8, 0x8c, 0xfe, 0xa1, 0xdf, 0x24, 0x8c, 0xbf, 0x02, 0x9c,
+  0xed, 0x99, 0xbe, 0x3a, 0x53, 0x2a, 0x45, 0x4e, 0xb0, 0x48, 0x78, 0x37,
+  0xdc, 0xa1, 0x63, 0xef, 0x03, 0xb7, 0x94, 0x29, 0x66, 0x6c, 0x66, 0xd7,
+  0x6c, 0x6a, 0x48, 0x65, 0xb2, 0xdd, 0x47, 0x21, 0x23, 0x1b, 0xb8, 0x41,
+  0x74, 0x2f, 0x96, 0xdd, 0x98, 0x22, 0xb9, 0xfa, 0xf3, 0x0e, 0x4a, 0xb0,
+  0x2f, 0x0b, 0xa2, 0xde, 0xb7, 0x02, 0x13, 0x42, 0x70, 0x3f, 0x78, 0x04,
+  0x14, 0x72, 0xe3, 0x3a, 0x2b, 0x7e, 0x28, 0x48, 0x1d, 0x96, 0xb4, 0xdb,
+  0x16, 0x39, 0x8d, 0xb3, 0xc4, 0x59, 0xa1, 0xd7, 0xa2, 0xd2, 0x64, 0x61,
+  0x33, 0x2b, 0x41, 0x18, 0xc5, 0xab, 0x95, 0x6f, 0x5d, 0x22, 0x07, 0x77,
+  0xcd, 0x53, 0x9d, 0x03, 0x49, 0x65, 0xd5, 0x88, 0xf5, 0x5f, 0x9d, 0xf0,
+  0xc4, 0x69, 0x4f, 0x91, 0x08, 0xa5, 0x39, 0x07, 0x96, 0x36, 0xaf, 0x2d,
+  0x64, 0xdc, 0x26, 0x5a, 0xc1, 0x13, 0xee, 0x31, 0x39, 0xd6, 0x5f, 0xdc,
+  0x97, 0xfc, 0x27, 0xaa, 0x05, 0x78, 0x47, 0xc5, 0x22, 0x26, 0x63, 0x53,
+  0x7e, 0x37, 0xc2, 0x7b, 0x64, 0x3d, 0x69, 0xcb, 0xf0, 0xfb, 0x8a, 0x15,
+  0x3e, 0x52, 0xb3, 0x86, 0x6a, 0xb4, 0xc1, 0x1c, 0x3b, 0xb2, 0xf5, 0xc7,
+  0x3e, 0xc5, 0x76, 0xdd, 0x74, 0x68, 0x76, 0x7a, 0x55, 0xe6, 0x80, 0x7b,
+  0x2e, 0x8c, 0xa6, 0xda, 0xbb, 0x91, 0x5a, 0x07, 0xcd, 0x19, 0x4a, 0xea,
+  0x08, 0x5c, 0xff, 0xc1, 0x49, 0xd6, 0x7b, 0x06, 0xbf, 0xeb, 0xb7, 0x4a,
+  0x9f, 0xb4, 0x27, 0x9c, 0x17, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x01, 0xda, 0x30, 0x82, 0x01, 0xd6, 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06,
+  0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x05, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0x30, 0x23, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82,
+  0x37, 0x15, 0x02, 0x04, 0x16, 0x04, 0x14, 0x23, 0xb7, 0xd0, 0xed, 0x68,
+  0x91, 0xef, 0x66, 0x9b, 0xe1, 0x05, 0x40, 0x7a, 0xbe, 0x67, 0xca, 0x33,
+  0xd6, 0x06, 0xbd, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+  0x04, 0x14, 0xeb, 0xdb, 0x11, 0x5e, 0xf8, 0x09, 0x9e, 0xd8, 0xd6, 0x62,
+  0x9c, 0xfd, 0x62, 0x9d, 0xe3, 0x84, 0x4a, 0x28, 0xe1, 0x27, 0x30, 0x19,
+  0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04,
+  0x0c, 0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00,
+  0x41, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x86, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1f,
+  0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x2a,
+  0x4d, 0x97, 0x95, 0x5d, 0x34, 0x7e, 0x9d, 0xb6, 0xe6, 0x33, 0xbe, 0x9c,
+  0x27, 0xc1, 0x70, 0x7e, 0x67, 0xdb, 0xc1, 0x30, 0x81, 0xa3, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x30, 0x81, 0x95,
+  0xa0, 0x81, 0x92, 0xa0, 0x81, 0x8f, 0x86, 0x36, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6d, 0x73, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63,
+  0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+  0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72,
+  0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63,
+  0x72, 0x6c, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63,
+  0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77,
+  0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x6c, 0x86, 0x1f, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70, 0x6b, 0x69,
+  0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x36,
+  0x29, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x79, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6d, 0x30, 0x6b, 0x30, 0x3c, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x30, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69,
+  0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x6d,
+  0x73, 0x77, 0x77, 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30,
+  0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
+  0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70,
+  0x70, 0x6b, 0x69, 0x2f, 0x61, 0x69, 0x61, 0x2f, 0x6d, 0x73, 0x77, 0x77,
+  0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x02, 0x01, 0x00, 0xa3, 0x36, 0x72, 0xf7, 0x45, 0x0b, 0xa7, 0x86,
+  0x37, 0xde, 0x20, 0x8d, 0xf5, 0xd7, 0x8e, 0xda, 0x89, 0x00, 0x7a, 0x52,
+  0x4b, 0x85, 0x32, 0xb2, 0x32, 0xd7, 0xed, 0x80, 0x57, 0xfd, 0x0d, 0x51,
+  0x1c, 0xd1, 0x1e, 0x3d, 0x0c, 0x3e, 0x91, 0x2a, 0x30, 0xe2, 0x53, 0xbc,
+  0x61, 0x07, 0x89, 0x02, 0x11, 0xb9, 0x1b, 0x83, 0x48, 0xd3, 0x1a, 0xc7,
+  0x47, 0x96, 0x62, 0xb7, 0xcf, 0xde, 0xd7, 0x0d, 0xb8, 0x9d, 0x84, 0x8e,
+  0xde, 0xd5, 0xe6, 0xe4, 0xb9, 0xc0, 0x06, 0xe3, 0x1c, 0xf4, 0x31, 0x43,
+  0x1f, 0x53, 0xfc, 0x4a, 0x42, 0xb5, 0x9b, 0x23, 0xcd, 0xb2, 0xec, 0x0d,
+  0x0f, 0x81, 0x89, 0xff, 0x59, 0xa9, 0x00, 0xd3, 0x04, 0x4a, 0x0b, 0xaf,
+  0x5b, 0x84, 0xf5, 0x3b, 0x4f, 0xb9, 0x37, 0x91, 0x88, 0x21, 0xe4, 0x9c,
+  0xca, 0x52, 0x76, 0x63, 0x7e, 0x88, 0x7a, 0x65, 0xb4, 0x8e, 0x16, 0x6a,
+  0xf4, 0x60, 0xbd, 0x2c, 0x0e, 0xef, 0x17, 0x86, 0x2b, 0x75, 0x09, 0x58,
+  0x73, 0xc5, 0x4f, 0xb9, 0xa8, 0x1b, 0xef, 0x2a, 0xf4, 0xb6, 0xa3, 0xb9,
+  0x07, 0xf0, 0xa4, 0x90, 0x39, 0x53, 0xdf, 0xe1, 0xba, 0x02, 0x98, 0xa5,
+  0xa5, 0x82, 0x11, 0xa1, 0x06, 0x53, 0xb2, 0xc2, 0xeb, 0xfd, 0xea, 0x67,
+  0x72, 0x37, 0x63, 0xd0, 0x85, 0xcf, 0x86, 0x05, 0xc8, 0xc3, 0x73, 0xc1,
+  0xdb, 0xf3, 0xc4, 0xd2, 0x55, 0xff, 0xa0, 0x1d, 0xe7, 0x72, 0x14, 0x1a,
+  0xff, 0xb2, 0xff, 0x08, 0x42, 0x48, 0x40, 0x0a, 0x19, 0x82, 0xcf, 0x22,
+  0xf3, 0x05, 0xc5, 0xd5, 0xdf, 0x0b, 0x29, 0xc4, 0x3a, 0x0e, 0xc5, 0x32,
+  0x38, 0xef, 0x2f, 0x94, 0x9a, 0x0c, 0xf2, 0xd4, 0xee, 0xbf, 0x62, 0xc7,
+  0xe2, 0xa4, 0x5f, 0x3d, 0x6e, 0x78, 0xbc, 0x10, 0xc1, 0x2e, 0x4a, 0x30,
+  0xf2, 0x87, 0xdb, 0x89, 0x38, 0xbe, 0xfe, 0xcc, 0x80, 0x8f, 0xf5, 0xf9,
+  0x5c, 0xcb, 0x5c, 0xf1, 0xea, 0x02, 0x08, 0x6b, 0xa5, 0x0b, 0xef, 0x20,
+  0x5c, 0xa3, 0x34, 0xcc, 0xf0, 0x80, 0xb6, 0x1f, 0x63, 0xb9, 0x44, 0x32,
+  0x1c, 0x26, 0x41, 0x9b, 0xdd, 0x93, 0xa3, 0x64, 0x01, 0x57, 0x11, 0x21,
+  0x43, 0x94, 0x2a, 0x57, 0x2d, 0x8a, 0x4a, 0xcb, 0x8a, 0x28, 0x40, 0x0a,
+  0xfb, 0x50, 0xf9, 0xaf, 0x26, 0x74, 0x13, 0x97, 0x82, 0x4e, 0x6c, 0x64,
+  0xeb, 0xd1, 0xc6, 0xbf, 0x5e, 0xfd, 0x25, 0xda, 0x05, 0x46, 0x4a, 0xae,
+  0xc7, 0x2f, 0xc7, 0x04, 0xef, 0x2e, 0x71, 0x2e, 0xe2, 0xa8, 0x5b, 0xa4,
+  0xea, 0xb2, 0x6f, 0xa8, 0x91, 0x35, 0xc4, 0xb7, 0x63, 0x17, 0x62, 0x0e,
+  0x27, 0x8e, 0x3c, 0x24, 0xcd, 0x3d, 0x45, 0x69, 0x05, 0xdc, 0x52, 0xc5,
+  0x35, 0xf8, 0x11, 0xc0, 0x1d, 0xdf, 0x62, 0x60, 0xf4, 0xe1, 0xa6, 0x5e,
+  0x70, 0xb8, 0x45, 0x74, 0x03, 0xab, 0xd1, 0x16, 0x74, 0xe3, 0x9e, 0xd3,
+  0xc1, 0xa3, 0xe8, 0x90, 0x96, 0x8a, 0x8a, 0xc2, 0x46, 0x46, 0x9d, 0xb9,
+  0x5c, 0x6c, 0x02, 0x1d, 0x32, 0x84, 0xeb, 0x14, 0x85, 0x76, 0x95, 0xaa,
+  0x4d, 0x2d, 0x69, 0xb6, 0x02, 0xf6, 0xfe, 0xed, 0x34, 0xd5, 0x8c, 0xe6,
+  0xfa, 0xac, 0x5d, 0xdc, 0x03, 0x40, 0xe6, 0xcf, 0x77, 0x89, 0xff, 0xb1,
+  0x28, 0xca, 0x86, 0x8c, 0xc8, 0xe7, 0x31, 0x47, 0xfc, 0x16, 0xfe, 0x54,
+  0x0c, 0xf5, 0x26, 0xb1, 0x7e, 0xdc, 0x98, 0x26, 0x70, 0x58, 0x26, 0x13,
+  0x5c, 0xc7, 0x75, 0xdb, 0x12, 0xde, 0x4c, 0xac, 0xff, 0x9a, 0x0c, 0xea,
+  0xa2, 0xc2, 0x1c, 0x41, 0x04, 0x8c, 0xe6, 0x47, 0x97, 0x47, 0x6f, 0x89,
+  0xc5, 0x48, 0xde, 0x37, 0x0d, 0x6a, 0xd9, 0xf0, 0x68, 0x24, 0x5c, 0xff,
+  0x19, 0x59, 0xe6, 0xe1, 0x70, 0x37, 0x38, 0x0d, 0xdb, 0xee, 0xb0, 0xe2,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            11:2a:00:6d:37:e5:10:6f:d6:ca:7c:c3:ef:ba:cc:18
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2016 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL SGC CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bd:56:88:ba:88:34:64:64:cf:cd:ca:b0:ee:e7:
+                    19:73:c5:72:d9:bb:45:bc:b5:a8:ff:83:be:1c:03:
+                    db:ed:89:b7:2e:10:1a:25:bc:55:ca:41:a1:9f:0b:
+                    cf:19:5e:70:b9:5e:39:4b:9e:31:1c:5f:87:ae:2a:
+                    aa:a8:2b:a2:1b:3b:10:23:5f:13:b1:dd:08:8c:4e:
+                    14:da:83:81:e3:b5:8c:e3:68:ed:24:67:ce:56:b6:
+                    ac:9b:73:96:44:db:8a:8c:b3:d6:f0:71:93:8e:db:
+                    71:54:4a:eb:73:59:6a:8f:70:51:2c:03:9f:97:d1:
+                    cc:11:7a:bc:62:0d:95:2a:c9:1c:75:57:e9:f5:c7:
+                    ea:ba:84:35:cb:c7:85:5a:7e:e4:4d:e1:11:97:7d:
+                    0e:20:34:45:db:f1:a2:09:eb:eb:3d:9e:b8:96:43:
+                    5e:34:4b:08:25:1e:43:1a:a2:d9:b7:8a:01:34:3d:
+                    c3:f8:e5:af:4f:8c:ff:cd:65:f0:23:4e:c5:97:b3:
+                    5c:da:90:1c:82:85:0d:06:0d:c1:22:b6:7b:28:a4:
+                    03:c3:4c:53:d1:58:bc:72:bc:08:39:fc:a0:76:a8:
+                    a8:e9:4b:6e:88:3d:e3:b3:31:25:8c:73:29:48:0e:
+                    32:79:06:ed:3d:43:f4:f6:e4:e9:fc:7d:be:8e:08:
+                    d5:1f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                4E:43:C8:1D:76:EF:37:53:7A:4F:F2:58:6F:94:F3:38:E2:D5:BD:DF
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+            X509v3 Extended Key Usage: 
+                Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            Netscape Cert Type: 
+                SSL CA, S/MIME CA
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=Class3CA2048-1-48
+            Authority Information Access: 
+                OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+    Signature Algorithm: sha1WithRSAEncryption
+         5a:a2:b1:bf:eb:8d:d4:38:a8:80:72:c2:dc:38:2e:ac:a7:71:
+         f9:2b:a3:bb:47:bb:6d:69:6f:10:36:98:8c:c7:56:2e:bb:bc:
+         ab:4a:9b:7a:d6:f2:82:93:e0:14:fe:8a:ce:83:b7:83:db:93:
+         87:ab:ac:65:79:49:fd:57:a9:b1:ce:09:1f:ba:10:15:c4:09:
+         0e:62:e3:f9:0a:25:d5:64:98:f0:f2:a8:0f:76:32:7e:91:e6:
+         18:ee:bc:e7:da:d0:4e:8d:78:bb:e2:9d:c0:59:2b:c0:ce:95:
+         0d:24:0c:72:ca:34:5e:70:22:89:2b:4a:b0:f1:68:87:f3:ee:
+         44:8d:28:40:77:39:6e:48:72:45:31:5d:6b:39:0e:86:02:ea:
+         66:99:93:31:0f:df:67:de:a6:9f:8c:9d:4c:ce:71:6f:3a:21:
+         f6:b9:34:3f:f9:6e:d8:9a:f7:3e:da:f3:81:5f:7a:5c:6d:8f:
+         7c:f6:99:74:b7:ff:e4:17:5d:ed:61:5e:ab:48:bb:96:8d:66:
+         45:39:b4:12:0a:f6:70:e9:9c:76:22:4b:60:e9:2a:1b:34:49:
+         f7:a2:d4:67:c0:b1:26:ad:13:ba:d9:84:01:c1:ab:e1:8e:6d:
+         70:16:3b:77:ac:91:9a:bb:1a:1f:da:58:a7:e4:4f:c1:61:ae:
+         bc:a2:fe:4b
+-----BEGIN CERTIFICATE-----
+MIIGCjCCBPKgAwIBAgIQESoAbTflEG/WynzD77rMGDANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBvjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMvVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9Voi6iDRkZM/NyrDu5xlzxXLZ
+u0W8taj/g74cA9vtibcuEBolvFXKQaGfC88ZXnC5XjlLnjEcX4euKqqoK6IbOxAj
+XxOx3QiMThTag4HjtYzjaO0kZ85Wtqybc5ZE24qMs9bwcZOO23FUSutzWWqPcFEs
+A5+X0cwRerxiDZUqyRx1V+n1x+q6hDXLx4VafuRN4RGXfQ4gNEXb8aIJ6+s9nriW
+Q140SwglHkMaotm3igE0PcP45a9PjP/NZfAjTsWXs1zakByChQ0GDcEitnsopAPD
+TFPRWLxyvAg5/KB2qKjpS26IPeOzMSWMcylIDjJ5Bu09Q/T25On8fb6OCNUfAgMB
+AAGjggH0MIIB8DAdBgNVHQ4EFgQUTkPIHXbvN1N6T/JYb5TzOOLVvd8wEgYDVR0T
+AQH/BAgwBgEB/wIBADA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczA9BgNVHR8ENjA0MDKgMKAuhixo
+dHRwOi8vRVZTZWN1cmUtY3JsLnZlcmlzaWduLmNvbS9wY2EzLWc1LmNybDAgBgNV
+HSUEGTAXBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwDgYDVR0PAQH/BAQDAgEGMBEG
+CWCGSAGG+EIBAQQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFn
+ZS9naWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRw
+Oi8vbG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjApBgNVHREEIjAgpB4wHDEa
+MBgGA1UEAxMRQ2xhc3MzQ0EyMDQ4LTEtNDgwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vRVZTZWN1cmUtb2NzcC52ZXJpc2lnbi5jb20wHwYDVR0j
+BBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZIhvcNAQEFBQADggEBAFqi
+sb/rjdQ4qIBywtw4Lqyncfkro7tHu21pbxA2mIzHVi67vKtKm3rW8oKT4BT+is6D
+t4Pbk4errGV5Sf1XqbHOCR+6EBXECQ5i4/kKJdVkmPDyqA92Mn6R5hjuvOfa0E6N
+eLvincBZK8DOlQ0kDHLKNF5wIokrSrDxaIfz7kSNKEB3OW5IckUxXWs5DoYC6maZ
+kzEP32fepp+MnUzOcW86Ifa5ND/5btia9z7a84Ffelxtj3z2mXS3/+QXXe1hXqtI
+u5aNZkU5tBIK9nDpnHYiS2DpKhs0Sfei1GfAsSatE7rZhAHBq+GObXAWO3eskZq7
+Gh/aWKfkT8Fhrryi/ks=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert91[] = {
+  0x30, 0x82, 0x06, 0x0a, 0x30, 0x82, 0x04, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x11, 0x2a, 0x00, 0x6d, 0x37, 0xe5, 0x10, 0x6f, 0xd6,
+  0xca, 0x7c, 0xc3, 0xef, 0xba, 0xcc, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+  0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+  0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x38,
+  0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+  0x4c, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x56, 0x88, 0xba, 0x88, 0x34, 0x64,
+  0x64, 0xcf, 0xcd, 0xca, 0xb0, 0xee, 0xe7, 0x19, 0x73, 0xc5, 0x72, 0xd9,
+  0xbb, 0x45, 0xbc, 0xb5, 0xa8, 0xff, 0x83, 0xbe, 0x1c, 0x03, 0xdb, 0xed,
+  0x89, 0xb7, 0x2e, 0x10, 0x1a, 0x25, 0xbc, 0x55, 0xca, 0x41, 0xa1, 0x9f,
+  0x0b, 0xcf, 0x19, 0x5e, 0x70, 0xb9, 0x5e, 0x39, 0x4b, 0x9e, 0x31, 0x1c,
+  0x5f, 0x87, 0xae, 0x2a, 0xaa, 0xa8, 0x2b, 0xa2, 0x1b, 0x3b, 0x10, 0x23,
+  0x5f, 0x13, 0xb1, 0xdd, 0x08, 0x8c, 0x4e, 0x14, 0xda, 0x83, 0x81, 0xe3,
+  0xb5, 0x8c, 0xe3, 0x68, 0xed, 0x24, 0x67, 0xce, 0x56, 0xb6, 0xac, 0x9b,
+  0x73, 0x96, 0x44, 0xdb, 0x8a, 0x8c, 0xb3, 0xd6, 0xf0, 0x71, 0x93, 0x8e,
+  0xdb, 0x71, 0x54, 0x4a, 0xeb, 0x73, 0x59, 0x6a, 0x8f, 0x70, 0x51, 0x2c,
+  0x03, 0x9f, 0x97, 0xd1, 0xcc, 0x11, 0x7a, 0xbc, 0x62, 0x0d, 0x95, 0x2a,
+  0xc9, 0x1c, 0x75, 0x57, 0xe9, 0xf5, 0xc7, 0xea, 0xba, 0x84, 0x35, 0xcb,
+  0xc7, 0x85, 0x5a, 0x7e, 0xe4, 0x4d, 0xe1, 0x11, 0x97, 0x7d, 0x0e, 0x20,
+  0x34, 0x45, 0xdb, 0xf1, 0xa2, 0x09, 0xeb, 0xeb, 0x3d, 0x9e, 0xb8, 0x96,
+  0x43, 0x5e, 0x34, 0x4b, 0x08, 0x25, 0x1e, 0x43, 0x1a, 0xa2, 0xd9, 0xb7,
+  0x8a, 0x01, 0x34, 0x3d, 0xc3, 0xf8, 0xe5, 0xaf, 0x4f, 0x8c, 0xff, 0xcd,
+  0x65, 0xf0, 0x23, 0x4e, 0xc5, 0x97, 0xb3, 0x5c, 0xda, 0x90, 0x1c, 0x82,
+  0x85, 0x0d, 0x06, 0x0d, 0xc1, 0x22, 0xb6, 0x7b, 0x28, 0xa4, 0x03, 0xc3,
+  0x4c, 0x53, 0xd1, 0x58, 0xbc, 0x72, 0xbc, 0x08, 0x39, 0xfc, 0xa0, 0x76,
+  0xa8, 0xa8, 0xe9, 0x4b, 0x6e, 0x88, 0x3d, 0xe3, 0xb3, 0x31, 0x25, 0x8c,
+  0x73, 0x29, 0x48, 0x0e, 0x32, 0x79, 0x06, 0xed, 0x3d, 0x43, 0xf4, 0xf6,
+  0xe4, 0xe9, 0xfc, 0x7d, 0xbe, 0x8e, 0x08, 0xd5, 0x1f, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x01, 0xf4, 0x30, 0x82, 0x01, 0xf0, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4e, 0x43, 0xc8,
+  0x1d, 0x76, 0xef, 0x37, 0x53, 0x7a, 0x4f, 0xf2, 0x58, 0x6f, 0x94, 0xf3,
+  0x38, 0xe2, 0xd5, 0xbd, 0xdf, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+  0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x63, 0x70, 0x73, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+  0x36, 0x30, 0x34, 0x30, 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33,
+  0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x20, 0x06, 0x03, 0x55,
+  0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+  0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86,
+  0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+  0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x11, 0x06,
+  0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04,
+  0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b,
+  0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67,
+  0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06,
+  0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a,
+  0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18,
+  0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c,
+  0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06, 0x03, 0x55,
+  0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a,
+  0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43, 0x6c, 0x61,
+  0x73, 0x73, 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d,
+  0x34, 0x38, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+  0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd,
+  0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33,
+  0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5a, 0xa2,
+  0xb1, 0xbf, 0xeb, 0x8d, 0xd4, 0x38, 0xa8, 0x80, 0x72, 0xc2, 0xdc, 0x38,
+  0x2e, 0xac, 0xa7, 0x71, 0xf9, 0x2b, 0xa3, 0xbb, 0x47, 0xbb, 0x6d, 0x69,
+  0x6f, 0x10, 0x36, 0x98, 0x8c, 0xc7, 0x56, 0x2e, 0xbb, 0xbc, 0xab, 0x4a,
+  0x9b, 0x7a, 0xd6, 0xf2, 0x82, 0x93, 0xe0, 0x14, 0xfe, 0x8a, 0xce, 0x83,
+  0xb7, 0x83, 0xdb, 0x93, 0x87, 0xab, 0xac, 0x65, 0x79, 0x49, 0xfd, 0x57,
+  0xa9, 0xb1, 0xce, 0x09, 0x1f, 0xba, 0x10, 0x15, 0xc4, 0x09, 0x0e, 0x62,
+  0xe3, 0xf9, 0x0a, 0x25, 0xd5, 0x64, 0x98, 0xf0, 0xf2, 0xa8, 0x0f, 0x76,
+  0x32, 0x7e, 0x91, 0xe6, 0x18, 0xee, 0xbc, 0xe7, 0xda, 0xd0, 0x4e, 0x8d,
+  0x78, 0xbb, 0xe2, 0x9d, 0xc0, 0x59, 0x2b, 0xc0, 0xce, 0x95, 0x0d, 0x24,
+  0x0c, 0x72, 0xca, 0x34, 0x5e, 0x70, 0x22, 0x89, 0x2b, 0x4a, 0xb0, 0xf1,
+  0x68, 0x87, 0xf3, 0xee, 0x44, 0x8d, 0x28, 0x40, 0x77, 0x39, 0x6e, 0x48,
+  0x72, 0x45, 0x31, 0x5d, 0x6b, 0x39, 0x0e, 0x86, 0x02, 0xea, 0x66, 0x99,
+  0x93, 0x31, 0x0f, 0xdf, 0x67, 0xde, 0xa6, 0x9f, 0x8c, 0x9d, 0x4c, 0xce,
+  0x71, 0x6f, 0x3a, 0x21, 0xf6, 0xb9, 0x34, 0x3f, 0xf9, 0x6e, 0xd8, 0x9a,
+  0xf7, 0x3e, 0xda, 0xf3, 0x81, 0x5f, 0x7a, 0x5c, 0x6d, 0x8f, 0x7c, 0xf6,
+  0x99, 0x74, 0xb7, 0xff, 0xe4, 0x17, 0x5d, 0xed, 0x61, 0x5e, 0xab, 0x48,
+  0xbb, 0x96, 0x8d, 0x66, 0x45, 0x39, 0xb4, 0x12, 0x0a, 0xf6, 0x70, 0xe9,
+  0x9c, 0x76, 0x22, 0x4b, 0x60, 0xe9, 0x2a, 0x1b, 0x34, 0x49, 0xf7, 0xa2,
+  0xd4, 0x67, 0xc0, 0xb1, 0x26, 0xad, 0x13, 0xba, 0xd9, 0x84, 0x01, 0xc1,
+  0xab, 0xe1, 0x8e, 0x6d, 0x70, 0x16, 0x3b, 0x77, 0xac, 0x91, 0x9a, 0xbb,
+  0x1a, 0x1f, 0xda, 0x58, 0xa7, 0xe4, 0x4f, 0xc1, 0x61, 0xae, 0xbc, 0xa2,
+  0xfe, 0x4b,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            61:03:33:36:00:05:00:00:00:30
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: CN=Microsoft Internet Authority
+        Validity
+            Not Before: May 19 22:13:30 2010 GMT
+            Not After : May 19 22:23:30 2014 GMT
+        Subject: DC=com, DC=microsoft, DC=corp, DC=redmond, CN=Microsoft Secure Server Authority
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:ea:9f:5f:91:0b:cd:19:82:5f:91:ea:ab:f5:8b:
+                    28:d8:8b:f5:1c:e0:91:c9:bc:cd:02:10:50:22:b7:
+                    38:0a:5c:cf:71:0c:58:2d:88:6c:a8:b8:3c:33:63:
+                    f9:73:9d:3c:e9:c3:79:ed:f2:fe:c9:cb:c3:6e:24:
+                    e2:3c:42:70:d8:5f:b7:5b:f7:9b:5f:f5:27:6f:78:
+                    00:eb:96:5d:b7:6f:cf:e4:41:04:f0:bb:43:bd:6f:
+                    5f:26:0f:b7:8e:37:41:13:54:67:1b:90:00:27:38:
+                    b8:1a:c3:96:6d:1c:31:35:35:49:c5:46:1e:e7:73:
+                    a4:ca:03:11:79:41:81:af:d3:8e:46:a2:c5:be:00:
+                    53:05:b9:38:9c:b7:60:29:b3:ca:52:9a:92:c5:53:
+                    27:b6:41:0d:40:f8:2f:9b:e7:81:49:1a:5a:6a:a8:
+                    4f:71:c7:e8:6d:81:be:27:ef:c9:d6:c6:92:2b:10:
+                    e4:36:35:40:08:d0:4d:70:fd:70:9b:20:1c:b3:b9:
+                    df:75:9d:2b:77:d0:c4:cd:6a:71:ef:5a:58:0b:f9:
+                    70:85:88:05:89:6d:66:92:30:ab:af:88:39:d7:d4:
+                    2d:0b:96:9c:78:24:af:00:ab:cf:09:3e:13:ae:6b:
+                    c3:e0:e1:cf:60:7f:8b:53:dc:02:d0:f3:b0:86:11:
+                    de:bd
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Subject Key Identifier: 
+                08:42:E3:DB:4E:11:66:F3:B5:08:C5:40:DB:55:7C:33:46:11:83:38
+            X509v3 Key Usage: 
+                Digital Signature, Certificate Sign, CRL Sign
+            1.3.6.1.4.1.311.21.1: 
+                .....
+            1.3.6.1.4.1.311.21.2: 
+                ..~...Z2..q..Oup......
+            1.3.6.1.4.1.311.20.2: 
+                .
+.S.u.b.C.A
+            X509v3 Authority Key Identifier: 
+                keyid:33:21:F0:CB:FE:A2:A0:44:92:DE:F6:3B:33:D8:5F:01:4B:97:78:5D
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://mscrl.microsoft.com/pki/mscorp/crl/mswww(5).crl
+                  URI:http://crl.microsoft.com/pki/mscorp/crl/mswww(5).crl
+                  URI:http://corppki/crl/mswww(5).crl
+
+            Authority Information Access: 
+                CA Issuers - URI:http://www.microsoft.com/pki/mscorp/mswww(5).crt
+                CA Issuers - URI:http://corppki/aia/mswww(5).crt
+
+    Signature Algorithm: sha1WithRSAEncryption
+         8f:c2:d1:5c:ef:14:11:77:17:63:07:3c:4c:7c:68:da:fe:86:
+         4a:e2:20:cc:3f:b0:27:3d:d1:e2:ac:c8:8b:48:a6:e4:59:f7:
+         3a:06:ad:7d:52:f1:f6:65:61:96:21:22:ae:68:be:2f:7a:de:
+         b3:0c:f5:e9:c5:dd:f8:65:82:5d:cb:6c:3e:0c:37:11:74:15:
+         09:78:55:bd:26:12:bb:d6:95:74:d3:bc:f5:76:09:2a:6a:df:
+         36:c4:8e:56:d5:1f:20:df:7f:82:30:d7:43:ab:68:22:8b:6a:
+         5a:c5:9b:d0:9d:8d:0b:0c:50:85:7e:cc:5a:80:07:8b:03:4e:
+         bf:bd:5f:6c:56:0f:05:a9:e2:54:c3:a5:d3:52:5c:5f:4d:0b:
+         dd:05:f8:51:12:03:21:6f:9c:6c:97:98:2a:c1:c1:11:bc:bd:
+         1b:ae:fb:e3:57:5f:4f:1f:00:9e:e2:a4:51:d3:f7:ac:09:37:
+         58:a5:09:21:d1:72:d0:b2:c1:8b:db:4d:dc:13:d1:54:58:4d:
+         2b:c0:ad:fa:53:19:35:b1:15:a8:42:64:b7:ed:c7:1f:a5:79:
+         a8:0d:38:d4:50:bf:f4:5a:ff:2f:e9:bf:3f:7d:38:e5:fb:20:
+         0c:d4:4e:e0:2f:1d:45:7a:fb:28:2f:31:48:6f:cc:6e:5c:68:
+         42:fa:ea:c8:0b:01:30:ec:10:26:42:38:23:a9:c3:19:b8:d9:
+         70:1a:68:2c:92:cb:9f:73:e6:cc:ff:33:23:ee:db:5e:b5:7f:
+         05:58:3f:50:c5:1c:08:18:f4:eb:2f:62:aa:53:f7:a1:cd:de:
+         e3:eb:82:1c:1a:67:6b:a1:4c:a7:68:71:40:d1:65:3b:41:18:
+         9c:49:e3:71:fb:eb:4d:83:93:d3:47:e6:64:42:cb:b6:35:1c:
+         fb:34:0e:a1:28:fb:8c:a1:a7:1f:01:28:51:e5:71:94:37:9c:
+         dc:41:5b:7c:7e:e9:2c:23:67:94:9d:73:df:5f:40:79:a3:8d:
+         95:30:cc:53:17:08:bc:50:86:f3:fc:10:19:81:fc:f4:5a:6e:
+         f3:dc:a2:9a:75:7b:c3:ac:a0:51:ed:32:b6:58:df:4f:8e:91:
+         53:6a:d2:aa:1b:5d:e6:53:b8:89:a3:9e:89:a1:e3:29:e0:b3:
+         6c:eb:1a:cc:6f:5a:aa:c2:e2:f6:1e:45:29:ef:d6:c2:43:b1:
+         3b:ad:3e:26:fc:81:97:5c:48:fd:62:59:34:92:c9:fb:b9:a1:
+         d7:42:05:fb:19:f6:7e:32:fb:29:34:d5:87:66:e5:04:1d:c8:
+         3e:10:fa:a6:78:f5:1e:7d:de:1a:3a:78:7c:dc:2a:71:06:a3:
+         2d:6f:05:55:23:8b:90:ef
+-----BEGIN CERTIFICATE-----
+MIIGEzCCA/ugAwIBAgIKYQMzNgAFAAAAMDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
+VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTEwMDUxOTIyMTMz
+MFoXDTE0MDUxOTIyMjMzMFowgYsxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
+kiaJk/IsZAEZFgltaWNyb3NvZnQxFDASBgoJkiaJk/IsZAEZFgRjb3JwMRcwFQYK
+CZImiZPyLGQBGRYHcmVkbW9uZDEqMCgGA1UEAxMhTWljcm9zb2Z0IFNlY3VyZSBT
+ZXJ2ZXIgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+6p9fkQvNGYJfkeqr9Yso2Iv1HOCRybzNAhBQIrc4ClzPcQxYLYhsqLg8M2P5c508
+6cN57fL+ycvDbiTiPEJw2F+3W/ebX/Unb3gA65Zdt2/P5EEE8LtDvW9fJg+3jjdB
+E1RnG5AAJzi4GsOWbRwxNTVJxUYe53OkygMReUGBr9OORqLFvgBTBbk4nLdgKbPK
+UpqSxVMntkENQPgvm+eBSRpaaqhPccfobYG+J+/J1saSKxDkNjVACNBNcP1wmyAc
+s7nfdZ0rd9DEzWpx71pYC/lwhYgFiW1mkjCrr4g519QtC5aceCSvAKvPCT4TrmvD
+4OHPYH+LU9wC0POwhhHevQIDAQABo4IB2jCCAdYwEgYDVR0TAQH/BAgwBgEB/wIB
+ADAdBgNVHQ4EFgQUCELj204RZvO1CMVA21V8M0YRgzgwCwYDVR0PBAQDAgGGMBIG
+CSsGAQQBgjcVAQQFAgMIAAgwIwYJKwYBBAGCNxUCBBYEFH6KwpxaMozCcaLZT3Vw
+96kb9pQFMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMB8GA1UdIwQYMBaAFDMh
+8Mv+oqBEkt72OzPYXwFLl3hdMIGjBgNVHR8EgZswgZgwgZWggZKggY+GNmh0dHA6
+Ly9tc2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3KDUpLmNy
+bIY0aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3
+KDUpLmNybIYfaHR0cDovL2NvcnBwa2kvY3JsL21zd3d3KDUpLmNybDB5BggrBgEF
+BQcBAQRtMGswPAYIKwYBBQUHMAKGMGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
+a2kvbXNjb3JwL21zd3d3KDUpLmNydDArBggrBgEFBQcwAoYfaHR0cDovL2NvcnBw
+a2kvYWlhL21zd3d3KDUpLmNydDANBgkqhkiG9w0BAQUFAAOCAgEAj8LRXO8UEXcX
+Ywc8THxo2v6GSuIgzD+wJz3R4qzIi0im5Fn3OgatfVLx9mVhliEirmi+L3reswz1
+6cXd+GWCXctsPgw3EXQVCXhVvSYSu9aVdNO89XYJKmrfNsSOVtUfIN9/gjDXQ6to
+IotqWsWb0J2NCwxQhX7MWoAHiwNOv71fbFYPBaniVMOl01JcX00L3QX4URIDIW+c
+bJeYKsHBEby9G67741dfTx8AnuKkUdP3rAk3WKUJIdFy0LLBi9tN3BPRVFhNK8Ct
++lMZNbEVqEJkt+3HH6V5qA041FC/9Fr/L+m/P3045fsgDNRO4C8dRXr7KC8xSG/M
+blxoQvrqyAsBMOwQJkI4I6nDGbjZcBpoLJLLn3PmzP8zI+7bXrV/BVg/UMUcCBj0
+6y9iqlP3oc3e4+uCHBpna6FMp2hxQNFlO0EYnEnjcfvrTYOT00fmZELLtjUc+zQO
+oSj7jKGnHwEoUeVxlDec3EFbfH7pLCNnlJ1z319AeaONlTDMUxcIvFCG8/wQGYH8
+9Fpu89yimnV7w6ygUe0ytljfT46RU2rSqhtd5lO4iaOeiaHjKeCzbOsazG9aqsLi
+9h5FKe/WwkOxO60+JvyBl1xI/WJZNJLJ+7mh10IF+xn2fjL7KTTVh2blBB3IPhD6
+pnj1Hn3eGjp4fNwqcQajLW8FVSOLkO8=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert92[] = {
+  0x30, 0x82, 0x06, 0x13, 0x30, 0x82, 0x03, 0xfb, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x0a, 0x61, 0x03, 0x33, 0x36, 0x00, 0x05, 0x00, 0x00, 0x00,
+  0x30, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x05, 0x05, 0x00, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03,
+  0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+  0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20,
+  0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+  0x0d, 0x31, 0x30, 0x30, 0x35, 0x31, 0x39, 0x32, 0x32, 0x31, 0x33, 0x33,
+  0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x35, 0x31, 0x39, 0x32, 0x32,
+  0x32, 0x33, 0x33, 0x30, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x13, 0x30, 0x11,
+  0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+  0x16, 0x03, 0x63, 0x6f, 0x6d, 0x31, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x09,
+  0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x09, 0x6d,
+  0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x31, 0x14, 0x30, 0x12,
+  0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+  0x16, 0x04, 0x63, 0x6f, 0x72, 0x70, 0x31, 0x17, 0x30, 0x15, 0x06, 0x0a,
+  0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x07,
+  0x72, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x2a, 0x30, 0x28, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+  0x6f, 0x66, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53,
+  0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+  0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+  0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+  0xea, 0x9f, 0x5f, 0x91, 0x0b, 0xcd, 0x19, 0x82, 0x5f, 0x91, 0xea, 0xab,
+  0xf5, 0x8b, 0x28, 0xd8, 0x8b, 0xf5, 0x1c, 0xe0, 0x91, 0xc9, 0xbc, 0xcd,
+  0x02, 0x10, 0x50, 0x22, 0xb7, 0x38, 0x0a, 0x5c, 0xcf, 0x71, 0x0c, 0x58,
+  0x2d, 0x88, 0x6c, 0xa8, 0xb8, 0x3c, 0x33, 0x63, 0xf9, 0x73, 0x9d, 0x3c,
+  0xe9, 0xc3, 0x79, 0xed, 0xf2, 0xfe, 0xc9, 0xcb, 0xc3, 0x6e, 0x24, 0xe2,
+  0x3c, 0x42, 0x70, 0xd8, 0x5f, 0xb7, 0x5b, 0xf7, 0x9b, 0x5f, 0xf5, 0x27,
+  0x6f, 0x78, 0x00, 0xeb, 0x96, 0x5d, 0xb7, 0x6f, 0xcf, 0xe4, 0x41, 0x04,
+  0xf0, 0xbb, 0x43, 0xbd, 0x6f, 0x5f, 0x26, 0x0f, 0xb7, 0x8e, 0x37, 0x41,
+  0x13, 0x54, 0x67, 0x1b, 0x90, 0x00, 0x27, 0x38, 0xb8, 0x1a, 0xc3, 0x96,
+  0x6d, 0x1c, 0x31, 0x35, 0x35, 0x49, 0xc5, 0x46, 0x1e, 0xe7, 0x73, 0xa4,
+  0xca, 0x03, 0x11, 0x79, 0x41, 0x81, 0xaf, 0xd3, 0x8e, 0x46, 0xa2, 0xc5,
+  0xbe, 0x00, 0x53, 0x05, 0xb9, 0x38, 0x9c, 0xb7, 0x60, 0x29, 0xb3, 0xca,
+  0x52, 0x9a, 0x92, 0xc5, 0x53, 0x27, 0xb6, 0x41, 0x0d, 0x40, 0xf8, 0x2f,
+  0x9b, 0xe7, 0x81, 0x49, 0x1a, 0x5a, 0x6a, 0xa8, 0x4f, 0x71, 0xc7, 0xe8,
+  0x6d, 0x81, 0xbe, 0x27, 0xef, 0xc9, 0xd6, 0xc6, 0x92, 0x2b, 0x10, 0xe4,
+  0x36, 0x35, 0x40, 0x08, 0xd0, 0x4d, 0x70, 0xfd, 0x70, 0x9b, 0x20, 0x1c,
+  0xb3, 0xb9, 0xdf, 0x75, 0x9d, 0x2b, 0x77, 0xd0, 0xc4, 0xcd, 0x6a, 0x71,
+  0xef, 0x5a, 0x58, 0x0b, 0xf9, 0x70, 0x85, 0x88, 0x05, 0x89, 0x6d, 0x66,
+  0x92, 0x30, 0xab, 0xaf, 0x88, 0x39, 0xd7, 0xd4, 0x2d, 0x0b, 0x96, 0x9c,
+  0x78, 0x24, 0xaf, 0x00, 0xab, 0xcf, 0x09, 0x3e, 0x13, 0xae, 0x6b, 0xc3,
+  0xe0, 0xe1, 0xcf, 0x60, 0x7f, 0x8b, 0x53, 0xdc, 0x02, 0xd0, 0xf3, 0xb0,
+  0x86, 0x11, 0xde, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+  0xda, 0x30, 0x82, 0x01, 0xd6, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x08, 0x42, 0xe3, 0xdb, 0x4e, 0x11, 0x66, 0xf3, 0xb5, 0x08, 0xc5, 0x40,
+  0xdb, 0x55, 0x7c, 0x33, 0x46, 0x11, 0x83, 0x38, 0x30, 0x0b, 0x06, 0x03,
+  0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x12, 0x06,
+  0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x05,
+  0x02, 0x03, 0x08, 0x00, 0x08, 0x30, 0x23, 0x06, 0x09, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x82, 0x37, 0x15, 0x02, 0x04, 0x16, 0x04, 0x14, 0x7e, 0x8a,
+  0xc2, 0x9c, 0x5a, 0x32, 0x8c, 0xc2, 0x71, 0xa2, 0xd9, 0x4f, 0x75, 0x70,
+  0xf7, 0xa9, 0x1b, 0xf6, 0x94, 0x05, 0x30, 0x19, 0x06, 0x09, 0x2b, 0x06,
+  0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, 0x1e, 0x0a, 0x00,
+  0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x33, 0x21,
+  0xf0, 0xcb, 0xfe, 0xa2, 0xa0, 0x44, 0x92, 0xde, 0xf6, 0x3b, 0x33, 0xd8,
+  0x5f, 0x01, 0x4b, 0x97, 0x78, 0x5d, 0x30, 0x81, 0xa3, 0x06, 0x03, 0x55,
+  0x1d, 0x1f, 0x04, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x30, 0x81, 0x95, 0xa0,
+  0x81, 0x92, 0xa0, 0x81, 0x8f, 0x86, 0x36, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x6d, 0x73, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72,
+  0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b,
+  0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c,
+  0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72,
+  0x6c, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f,
+  0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77,
+  0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x6c, 0x86, 0x1f, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70, 0x6b, 0x69, 0x2f,
+  0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x35, 0x29,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x79, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x6d, 0x30, 0x6b, 0x30, 0x3c, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x30, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,
+  0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+  0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x6d, 0x73,
+  0x77, 0x77, 0x77, 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x2b,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x1f,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70,
+  0x6b, 0x69, 0x2f, 0x61, 0x69, 0x61, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77,
+  0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+  0x02, 0x01, 0x00, 0x8f, 0xc2, 0xd1, 0x5c, 0xef, 0x14, 0x11, 0x77, 0x17,
+  0x63, 0x07, 0x3c, 0x4c, 0x7c, 0x68, 0xda, 0xfe, 0x86, 0x4a, 0xe2, 0x20,
+  0xcc, 0x3f, 0xb0, 0x27, 0x3d, 0xd1, 0xe2, 0xac, 0xc8, 0x8b, 0x48, 0xa6,
+  0xe4, 0x59, 0xf7, 0x3a, 0x06, 0xad, 0x7d, 0x52, 0xf1, 0xf6, 0x65, 0x61,
+  0x96, 0x21, 0x22, 0xae, 0x68, 0xbe, 0x2f, 0x7a, 0xde, 0xb3, 0x0c, 0xf5,
+  0xe9, 0xc5, 0xdd, 0xf8, 0x65, 0x82, 0x5d, 0xcb, 0x6c, 0x3e, 0x0c, 0x37,
+  0x11, 0x74, 0x15, 0x09, 0x78, 0x55, 0xbd, 0x26, 0x12, 0xbb, 0xd6, 0x95,
+  0x74, 0xd3, 0xbc, 0xf5, 0x76, 0x09, 0x2a, 0x6a, 0xdf, 0x36, 0xc4, 0x8e,
+  0x56, 0xd5, 0x1f, 0x20, 0xdf, 0x7f, 0x82, 0x30, 0xd7, 0x43, 0xab, 0x68,
+  0x22, 0x8b, 0x6a, 0x5a, 0xc5, 0x9b, 0xd0, 0x9d, 0x8d, 0x0b, 0x0c, 0x50,
+  0x85, 0x7e, 0xcc, 0x5a, 0x80, 0x07, 0x8b, 0x03, 0x4e, 0xbf, 0xbd, 0x5f,
+  0x6c, 0x56, 0x0f, 0x05, 0xa9, 0xe2, 0x54, 0xc3, 0xa5, 0xd3, 0x52, 0x5c,
+  0x5f, 0x4d, 0x0b, 0xdd, 0x05, 0xf8, 0x51, 0x12, 0x03, 0x21, 0x6f, 0x9c,
+  0x6c, 0x97, 0x98, 0x2a, 0xc1, 0xc1, 0x11, 0xbc, 0xbd, 0x1b, 0xae, 0xfb,
+  0xe3, 0x57, 0x5f, 0x4f, 0x1f, 0x00, 0x9e, 0xe2, 0xa4, 0x51, 0xd3, 0xf7,
+  0xac, 0x09, 0x37, 0x58, 0xa5, 0x09, 0x21, 0xd1, 0x72, 0xd0, 0xb2, 0xc1,
+  0x8b, 0xdb, 0x4d, 0xdc, 0x13, 0xd1, 0x54, 0x58, 0x4d, 0x2b, 0xc0, 0xad,
+  0xfa, 0x53, 0x19, 0x35, 0xb1, 0x15, 0xa8, 0x42, 0x64, 0xb7, 0xed, 0xc7,
+  0x1f, 0xa5, 0x79, 0xa8, 0x0d, 0x38, 0xd4, 0x50, 0xbf, 0xf4, 0x5a, 0xff,
+  0x2f, 0xe9, 0xbf, 0x3f, 0x7d, 0x38, 0xe5, 0xfb, 0x20, 0x0c, 0xd4, 0x4e,
+  0xe0, 0x2f, 0x1d, 0x45, 0x7a, 0xfb, 0x28, 0x2f, 0x31, 0x48, 0x6f, 0xcc,
+  0x6e, 0x5c, 0x68, 0x42, 0xfa, 0xea, 0xc8, 0x0b, 0x01, 0x30, 0xec, 0x10,
+  0x26, 0x42, 0x38, 0x23, 0xa9, 0xc3, 0x19, 0xb8, 0xd9, 0x70, 0x1a, 0x68,
+  0x2c, 0x92, 0xcb, 0x9f, 0x73, 0xe6, 0xcc, 0xff, 0x33, 0x23, 0xee, 0xdb,
+  0x5e, 0xb5, 0x7f, 0x05, 0x58, 0x3f, 0x50, 0xc5, 0x1c, 0x08, 0x18, 0xf4,
+  0xeb, 0x2f, 0x62, 0xaa, 0x53, 0xf7, 0xa1, 0xcd, 0xde, 0xe3, 0xeb, 0x82,
+  0x1c, 0x1a, 0x67, 0x6b, 0xa1, 0x4c, 0xa7, 0x68, 0x71, 0x40, 0xd1, 0x65,
+  0x3b, 0x41, 0x18, 0x9c, 0x49, 0xe3, 0x71, 0xfb, 0xeb, 0x4d, 0x83, 0x93,
+  0xd3, 0x47, 0xe6, 0x64, 0x42, 0xcb, 0xb6, 0x35, 0x1c, 0xfb, 0x34, 0x0e,
+  0xa1, 0x28, 0xfb, 0x8c, 0xa1, 0xa7, 0x1f, 0x01, 0x28, 0x51, 0xe5, 0x71,
+  0x94, 0x37, 0x9c, 0xdc, 0x41, 0x5b, 0x7c, 0x7e, 0xe9, 0x2c, 0x23, 0x67,
+  0x94, 0x9d, 0x73, 0xdf, 0x5f, 0x40, 0x79, 0xa3, 0x8d, 0x95, 0x30, 0xcc,
+  0x53, 0x17, 0x08, 0xbc, 0x50, 0x86, 0xf3, 0xfc, 0x10, 0x19, 0x81, 0xfc,
+  0xf4, 0x5a, 0x6e, 0xf3, 0xdc, 0xa2, 0x9a, 0x75, 0x7b, 0xc3, 0xac, 0xa0,
+  0x51, 0xed, 0x32, 0xb6, 0x58, 0xdf, 0x4f, 0x8e, 0x91, 0x53, 0x6a, 0xd2,
+  0xaa, 0x1b, 0x5d, 0xe6, 0x53, 0xb8, 0x89, 0xa3, 0x9e, 0x89, 0xa1, 0xe3,
+  0x29, 0xe0, 0xb3, 0x6c, 0xeb, 0x1a, 0xcc, 0x6f, 0x5a, 0xaa, 0xc2, 0xe2,
+  0xf6, 0x1e, 0x45, 0x29, 0xef, 0xd6, 0xc2, 0x43, 0xb1, 0x3b, 0xad, 0x3e,
+  0x26, 0xfc, 0x81, 0x97, 0x5c, 0x48, 0xfd, 0x62, 0x59, 0x34, 0x92, 0xc9,
+  0xfb, 0xb9, 0xa1, 0xd7, 0x42, 0x05, 0xfb, 0x19, 0xf6, 0x7e, 0x32, 0xfb,
+  0x29, 0x34, 0xd5, 0x87, 0x66, 0xe5, 0x04, 0x1d, 0xc8, 0x3e, 0x10, 0xfa,
+  0xa6, 0x78, 0xf5, 0x1e, 0x7d, 0xde, 0x1a, 0x3a, 0x78, 0x7c, 0xdc, 0x2a,
+  0x71, 0x06, 0xa3, 0x2d, 0x6f, 0x05, 0x55, 0x23, 0x8b, 0x90, 0xef,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            2c:48:dd:93:0d:f5:59:8e:f9:3c:99:54:7a:60:ed:43
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Nov  8 00:00:00 2006 GMT
+            Not After : Nov  7 23:59:59 2016 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL SGC CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bd:56:88:ba:88:34:64:64:cf:cd:ca:b0:ee:e7:
+                    19:73:c5:72:d9:bb:45:bc:b5:a8:ff:83:be:1c:03:
+                    db:ed:89:b7:2e:10:1a:25:bc:55:ca:41:a1:9f:0b:
+                    cf:19:5e:70:b9:5e:39:4b:9e:31:1c:5f:87:ae:2a:
+                    aa:a8:2b:a2:1b:3b:10:23:5f:13:b1:dd:08:8c:4e:
+                    14:da:83:81:e3:b5:8c:e3:68:ed:24:67:ce:56:b6:
+                    ac:9b:73:96:44:db:8a:8c:b3:d6:f0:71:93:8e:db:
+                    71:54:4a:eb:73:59:6a:8f:70:51:2c:03:9f:97:d1:
+                    cc:11:7a:bc:62:0d:95:2a:c9:1c:75:57:e9:f5:c7:
+                    ea:ba:84:35:cb:c7:85:5a:7e:e4:4d:e1:11:97:7d:
+                    0e:20:34:45:db:f1:a2:09:eb:eb:3d:9e:b8:96:43:
+                    5e:34:4b:08:25:1e:43:1a:a2:d9:b7:8a:01:34:3d:
+                    c3:f8:e5:af:4f:8c:ff:cd:65:f0:23:4e:c5:97:b3:
+                    5c:da:90:1c:82:85:0d:06:0d:c1:22:b6:7b:28:a4:
+                    03:c3:4c:53:d1:58:bc:72:bc:08:39:fc:a0:76:a8:
+                    a8:e9:4b:6e:88:3d:e3:b3:31:25:8c:73:29:48:0e:
+                    32:79:06:ed:3d:43:f4:f6:e4:e9:fc:7d:be:8e:08:
+                    d5:1f
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                4E:43:C8:1D:76:EF:37:53:7A:4F:F2:58:6F:94:F3:38:E2:D5:BD:DF
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: X509v3 Any Policy
+                  CPS: https://www.verisign.com/cps
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            Netscape Cert Type: 
+                SSL CA, S/MIME CA
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=Class3CA2048-1-48
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+            Authority Information Access: 
+                OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+            X509v3 Extended Key Usage: 
+                Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+    Signature Algorithm: sha1WithRSAEncryption
+         27:74:a6:34:ea:1d:9d:e1:53:d6:1c:9d:0c:a7:5b:4c:a9:67:
+         f2:f0:32:b7:01:0f:fb:42:18:38:de:e4:ee:49:c8:13:c9:0b:
+         ec:04:c3:40:71:18:72:76:43:02:23:5d:ab:7b:c8:48:14:1a:
+         c8:7b:1d:fc:f6:0a:9f:36:a1:d2:09:73:71:66:96:75:51:34:
+         bf:99:30:51:67:9d:54:b7:26:45:ac:73:08:23:86:26:99:71:
+         f4:8e:d7:ea:39:9b:06:09:23:bf:62:dd:a8:c4:b6:7d:a4:89:
+         07:3e:f3:6d:ae:40:59:50:79:97:37:3d:32:78:7d:b2:63:4b:
+         f9:ea:08:69:0e:13:ed:e8:cf:bb:ac:05:86:ca:22:cf:88:62:
+         5d:3c:22:49:d8:63:d5:24:a6:bd:ef:5c:e3:cc:20:3b:22:ea:
+         fc:44:c6:a8:e5:1f:e1:86:cd:0c:4d:8f:93:53:d9:7f:ee:a1:
+         08:a7:b3:30:96:49:70:6e:a3:6c:3d:d0:63:ef:25:66:63:cc:
+         aa:b7:18:17:4e:ea:70:76:f6:ba:42:a6:80:37:09:4e:9f:66:
+         88:2e:6b:33:66:c8:c0:71:a4:41:eb:5a:e3:fc:14:2e:4b:88:
+         fd:ae:6e:5b:65:e9:27:e4:bf:e4:b0:23:c1:b2:7d:5b:62:25:
+         d7:3e:10:d4
+-----BEGIN CERTIFICATE-----
+MIIGHjCCBQagAwIBAgIQLEjdkw31WY75PJlUemDtQzANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBvjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMvVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9Voi6iDRkZM/NyrDu5xlzxXLZ
+u0W8taj/g74cA9vtibcuEBolvFXKQaGfC88ZXnC5XjlLnjEcX4euKqqoK6IbOxAj
+XxOx3QiMThTag4HjtYzjaO0kZ85Wtqybc5ZE24qMs9bwcZOO23FUSutzWWqPcFEs
+A5+X0cwRerxiDZUqyRx1V+n1x+q6hDXLx4VafuRN4RGXfQ4gNEXb8aIJ6+s9nriW
+Q140SwglHkMaotm3igE0PcP45a9PjP/NZfAjTsWXs1zakByChQ0GDcEitnsopAPD
+TFPRWLxyvAg5/KB2qKjpS26IPeOzMSWMcylIDjJ5Bu09Q/T25On8fb6OCNUfAgMB
+AAGjggIIMIICBDAdBgNVHQ4EFgQUTkPIHXbvN1N6T/JYb5TzOOLVvd8wEgYDVR0T
+AQH/BAgwBgEB/wIBADA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczA9BgNVHR8ENjA0MDKgMKAuhixo
+dHRwOi8vRVZTZWN1cmUtY3JsLnZlcmlzaWduLmNvbS9wY2EzLWc1LmNybDAOBgNV
+HQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMG0GCCsGAQUFBwEMBGEwX6Fd
+oFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrU
+SBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMCkG
+A1UdEQQiMCCkHjAcMRowGAYDVQQDExFDbGFzczNDQTIwNDgtMS00ODAfBgNVHSME
+GDAWgBR/02Wnwt3su/AwCfNDOfoCrzMxMzA9BggrBgEFBQcBAQQxMC8wLQYIKwYB
+BQUHMAGGIWh0dHA6Ly9FVlNlY3VyZS1vY3NwLnZlcmlzaWduLmNvbTA0BgNVHSUE
+LTArBglghkgBhvhCBAEGCmCGSAGG+EUBCAEGCCsGAQUFBwMBBggrBgEFBQcDAjAN
+BgkqhkiG9w0BAQUFAAOCAQEAJ3SmNOodneFT1hydDKdbTKln8vAytwEP+0IYON7k
+7knIE8kL7ATDQHEYcnZDAiNdq3vISBQayHsd/PYKnzah0glzcWaWdVE0v5kwUWed
+VLcmRaxzCCOGJplx9I7X6jmbBgkjv2LdqMS2faSJBz7zba5AWVB5lzc9Mnh9smNL
++eoIaQ4T7ejPu6wFhsoiz4hiXTwiSdhj1SSmve9c48wgOyLq/ETGqOUf4YbNDE2P
+k1PZf+6hCKezMJZJcG6jbD3QY+8lZmPMqrcYF07qcHb2ukKmgDcJTp9miC5rM2bI
+wHGkQeta4/wULkuI/a5uW2XpJ+S/5LAjwbJ9W2Il1z4Q1A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert93[] = {
+  0x30, 0x82, 0x06, 0x1e, 0x30, 0x82, 0x05, 0x06, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x2c, 0x48, 0xdd, 0x93, 0x0d, 0xf5, 0x59, 0x8e, 0xf9,
+  0x3c, 0x99, 0x54, 0x7a, 0x60, 0xed, 0x43, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+  0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+  0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x38,
+  0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+  0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+  0x4c, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+  0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+  0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+  0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x56, 0x88, 0xba, 0x88, 0x34, 0x64,
+  0x64, 0xcf, 0xcd, 0xca, 0xb0, 0xee, 0xe7, 0x19, 0x73, 0xc5, 0x72, 0xd9,
+  0xbb, 0x45, 0xbc, 0xb5, 0xa8, 0xff, 0x83, 0xbe, 0x1c, 0x03, 0xdb, 0xed,
+  0x89, 0xb7, 0x2e, 0x10, 0x1a, 0x25, 0xbc, 0x55, 0xca, 0x41, 0xa1, 0x9f,
+  0x0b, 0xcf, 0x19, 0x5e, 0x70, 0xb9, 0x5e, 0x39, 0x4b, 0x9e, 0x31, 0x1c,
+  0x5f, 0x87, 0xae, 0x2a, 0xaa, 0xa8, 0x2b, 0xa2, 0x1b, 0x3b, 0x10, 0x23,
+  0x5f, 0x13, 0xb1, 0xdd, 0x08, 0x8c, 0x4e, 0x14, 0xda, 0x83, 0x81, 0xe3,
+  0xb5, 0x8c, 0xe3, 0x68, 0xed, 0x24, 0x67, 0xce, 0x56, 0xb6, 0xac, 0x9b,
+  0x73, 0x96, 0x44, 0xdb, 0x8a, 0x8c, 0xb3, 0xd6, 0xf0, 0x71, 0x93, 0x8e,
+  0xdb, 0x71, 0x54, 0x4a, 0xeb, 0x73, 0x59, 0x6a, 0x8f, 0x70, 0x51, 0x2c,
+  0x03, 0x9f, 0x97, 0xd1, 0xcc, 0x11, 0x7a, 0xbc, 0x62, 0x0d, 0x95, 0x2a,
+  0xc9, 0x1c, 0x75, 0x57, 0xe9, 0xf5, 0xc7, 0xea, 0xba, 0x84, 0x35, 0xcb,
+  0xc7, 0x85, 0x5a, 0x7e, 0xe4, 0x4d, 0xe1, 0x11, 0x97, 0x7d, 0x0e, 0x20,
+  0x34, 0x45, 0xdb, 0xf1, 0xa2, 0x09, 0xeb, 0xeb, 0x3d, 0x9e, 0xb8, 0x96,
+  0x43, 0x5e, 0x34, 0x4b, 0x08, 0x25, 0x1e, 0x43, 0x1a, 0xa2, 0xd9, 0xb7,
+  0x8a, 0x01, 0x34, 0x3d, 0xc3, 0xf8, 0xe5, 0xaf, 0x4f, 0x8c, 0xff, 0xcd,
+  0x65, 0xf0, 0x23, 0x4e, 0xc5, 0x97, 0xb3, 0x5c, 0xda, 0x90, 0x1c, 0x82,
+  0x85, 0x0d, 0x06, 0x0d, 0xc1, 0x22, 0xb6, 0x7b, 0x28, 0xa4, 0x03, 0xc3,
+  0x4c, 0x53, 0xd1, 0x58, 0xbc, 0x72, 0xbc, 0x08, 0x39, 0xfc, 0xa0, 0x76,
+  0xa8, 0xa8, 0xe9, 0x4b, 0x6e, 0x88, 0x3d, 0xe3, 0xb3, 0x31, 0x25, 0x8c,
+  0x73, 0x29, 0x48, 0x0e, 0x32, 0x79, 0x06, 0xed, 0x3d, 0x43, 0xf4, 0xf6,
+  0xe4, 0xe9, 0xfc, 0x7d, 0xbe, 0x8e, 0x08, 0xd5, 0x1f, 0x02, 0x03, 0x01,
+  0x00, 0x01, 0xa3, 0x82, 0x02, 0x08, 0x30, 0x82, 0x02, 0x04, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4e, 0x43, 0xc8,
+  0x1d, 0x76, 0xef, 0x37, 0x53, 0x7a, 0x4f, 0xf2, 0x58, 0x6f, 0x94, 0xf3,
+  0x38, 0xe2, 0xd5, 0xbd, 0xdf, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+  0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+  0x00, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+  0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x63, 0x70, 0x73, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+  0x36, 0x30, 0x34, 0x30, 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75,
+  0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+  0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33,
+  0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+  0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+  0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01,
+  0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d,
+  0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d,
+  0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30,
+  0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5,
+  0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4,
+  0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65,
+  0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76,
+  0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06,
+  0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c,
+  0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43,
+  0x6c, 0x61, 0x73, 0x73, 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d,
+  0x31, 0x2d, 0x34, 0x38, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec,
+  0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31,
+  0x33, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+  0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f,
+  0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e,
+  0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+  0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+  0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+  0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x27, 0x74, 0xa6, 0x34, 0xea, 0x1d,
+  0x9d, 0xe1, 0x53, 0xd6, 0x1c, 0x9d, 0x0c, 0xa7, 0x5b, 0x4c, 0xa9, 0x67,
+  0xf2, 0xf0, 0x32, 0xb7, 0x01, 0x0f, 0xfb, 0x42, 0x18, 0x38, 0xde, 0xe4,
+  0xee, 0x49, 0xc8, 0x13, 0xc9, 0x0b, 0xec, 0x04, 0xc3, 0x40, 0x71, 0x18,
+  0x72, 0x76, 0x43, 0x02, 0x23, 0x5d, 0xab, 0x7b, 0xc8, 0x48, 0x14, 0x1a,
+  0xc8, 0x7b, 0x1d, 0xfc, 0xf6, 0x0a, 0x9f, 0x36, 0xa1, 0xd2, 0x09, 0x73,
+  0x71, 0x66, 0x96, 0x75, 0x51, 0x34, 0xbf, 0x99, 0x30, 0x51, 0x67, 0x9d,
+  0x54, 0xb7, 0x26, 0x45, 0xac, 0x73, 0x08, 0x23, 0x86, 0x26, 0x99, 0x71,
+  0xf4, 0x8e, 0xd7, 0xea, 0x39, 0x9b, 0x06, 0x09, 0x23, 0xbf, 0x62, 0xdd,
+  0xa8, 0xc4, 0xb6, 0x7d, 0xa4, 0x89, 0x07, 0x3e, 0xf3, 0x6d, 0xae, 0x40,
+  0x59, 0x50, 0x79, 0x97, 0x37, 0x3d, 0x32, 0x78, 0x7d, 0xb2, 0x63, 0x4b,
+  0xf9, 0xea, 0x08, 0x69, 0x0e, 0x13, 0xed, 0xe8, 0xcf, 0xbb, 0xac, 0x05,
+  0x86, 0xca, 0x22, 0xcf, 0x88, 0x62, 0x5d, 0x3c, 0x22, 0x49, 0xd8, 0x63,
+  0xd5, 0x24, 0xa6, 0xbd, 0xef, 0x5c, 0xe3, 0xcc, 0x20, 0x3b, 0x22, 0xea,
+  0xfc, 0x44, 0xc6, 0xa8, 0xe5, 0x1f, 0xe1, 0x86, 0xcd, 0x0c, 0x4d, 0x8f,
+  0x93, 0x53, 0xd9, 0x7f, 0xee, 0xa1, 0x08, 0xa7, 0xb3, 0x30, 0x96, 0x49,
+  0x70, 0x6e, 0xa3, 0x6c, 0x3d, 0xd0, 0x63, 0xef, 0x25, 0x66, 0x63, 0xcc,
+  0xaa, 0xb7, 0x18, 0x17, 0x4e, 0xea, 0x70, 0x76, 0xf6, 0xba, 0x42, 0xa6,
+  0x80, 0x37, 0x09, 0x4e, 0x9f, 0x66, 0x88, 0x2e, 0x6b, 0x33, 0x66, 0xc8,
+  0xc0, 0x71, 0xa4, 0x41, 0xeb, 0x5a, 0xe3, 0xfc, 0x14, 0x2e, 0x4b, 0x88,
+  0xfd, 0xae, 0x6e, 0x5b, 0x65, 0xe9, 0x27, 0xe4, 0xbf, 0xe4, 0xb0, 0x23,
+  0xc1, 0xb2, 0x7d, 0x5b, 0x62, 0x25, 0xd7, 0x3e, 0x10, 0xd4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            64:1b:e8:20:ce:02:08:13:f3:2d:4d:2d:95:d6:7e:67
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+        Validity
+            Not Before: Feb  8 00:00:00 2010 GMT
+            Not After : Feb  7 23:59:59 2020 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 International Server CA - G3
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:99:d6:9c:62:f0:15:f4:81:9a:41:08:59:8f:13:
+                    9d:17:c9:9f:51:dc:da:b1:52:ef:ff:e3:41:dd:e0:
+                    df:c4:28:c6:e3:ad:79:1f:27:10:98:b8:bb:20:97:
+                    c1:28:44:41:0f:ea:a9:a8:52:cf:4d:4e:1b:8b:bb:
+                    b5:c4:76:d9:cc:56:06:ee:b3:55:20:2a:de:15:8d:
+                    71:cb:54:c8:6f:17:cd:89:00:e4:dc:ff:e1:c0:1f:
+                    68:71:e9:c7:29:2e:7e:bc:3b:fc:e5:bb:ab:26:54:
+                    8b:66:90:cd:f6:92:b9:31:24:80:bc:9e:6c:d5:fc:
+                    7e:d2:e1:4b:8c:dc:42:fa:44:4b:5f:f8:18:b5:2e:
+                    30:f4:3d:12:98:d3:62:05:73:54:a6:9c:a2:1d:be:
+                    52:83:3a:07:46:c4:3b:02:56:21:bf:f2:51:4f:d0:
+                    a6:99:39:e9:ae:a5:3f:89:9b:9c:7d:fe:4d:60:07:
+                    25:20:f7:bb:d7:69:83:2b:82:93:43:37:d9:83:41:
+                    1b:6b:0b:ab:4a:66:84:4f:4a:8e:de:7e:34:99:8e:
+                    68:d6:ca:39:06:9b:4c:b3:9a:48:4d:13:46:b4:58:
+                    21:04:c4:fb:a0:4d:ac:2e:4b:62:12:e3:fb:4d:f6:
+                    c9:51:00:01:1f:fc:1e:6a:81:2a:38:e0:b9:4f:d6:
+                    2d:45
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.113733.1.7.23.3
+                  CPS: https://www.verisign.com/cps
+                  User Notice:
+                    Explicit Text: https://www.verisign.com/rpa
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3-g5.crl
+
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=VeriSignMPKI-2-7
+            X509v3 Subject Key Identifier: 
+                D7:9B:7C:D8:22:A0:15:F7:DD:AD:5F:CE:29:9B:58:C3:BC:46:00:B5
+            X509v3 Authority Key Identifier: 
+                keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+    Signature Algorithm: sha1WithRSAEncryption
+         71:b5:7d:73:52:4a:dd:d7:4d:34:2b:2e:af:94:46:a5:49:50:
+         02:4f:f8:2f:17:70:f2:13:dc:1f:21:86:aa:c2:4f:7c:37:3c:
+         d4:46:78:ae:5d:78:6f:d1:ba:5a:bc:10:ab:58:36:c5:8c:62:
+         15:45:60:17:21:e2:d5:42:a8:77:a1:55:d8:43:04:51:f6:6e:
+         ba:48:e6:5d:4c:b7:44:d3:3e:a4:d5:d6:33:9a:9f:0d:e6:d7:
+         4e:96:44:95:5a:6c:d6:a3:16:53:0e:98:43:ce:a4:b8:c3:66:
+         7a:05:5c:62:10:e8:1b:12:db:7d:2e:76:50:ff:df:d7:6b:1b:
+         cc:8a:cc:71:fa:b3:40:56:7c:33:7a:77:94:5b:f5:0b:53:fb:
+         0e:5f:bc:68:fb:af:2a:ee:30:37:79:16:93:25:7f:4d:10:ff:
+         57:fb:bf:6e:3b:33:21:de:79:dc:86:17:59:2d:43:64:b7:a6:
+         66:87:ea:bc:96:46:19:1a:86:8b:6f:d7:b7:49:00:5b:db:a3:
+         bf:29:9a:ee:f7:d3:33:ae:a3:f4:9e:4c:ca:5e:69:d4:1b:ad:
+         b7:90:77:6a:d8:59:6f:79:ab:01:fa:55:f0:8a:21:66:e5:65:
+         6e:fd:7c:d3:df:1e:eb:7e:3f:06:90:fb:19:0b:d3:06:02:1b:
+         78:43:99:a8
+-----BEGIN CERTIFICATE-----
+MIIGKTCCBRGgAwIBAgIQZBvoIM4CCBPzLU0tldZ+ZzANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBvDEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDE2MDQGA1UEAxMtVmVy
+aVNpZ24gQ2xhc3MgMyBJbnRlcm5hdGlvbmFsIFNlcnZlciBDQSAtIEczMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmdacYvAV9IGaQQhZjxOdF8mfUdza
+sVLv/+NB3eDfxCjG4615HycQmLi7IJfBKERBD+qpqFLPTU4bi7u1xHbZzFYG7rNV
+ICreFY1xy1TIbxfNiQDk3P/hwB9ocenHKS5+vDv85burJlSLZpDN9pK5MSSAvJ5s
+1fx+0uFLjNxC+kRLX/gYtS4w9D0SmNNiBXNUppyiHb5SgzoHRsQ7AlYhv/JRT9Cm
+mTnprqU/iZucff5NYAclIPe712mDK4KTQzfZg0EbawurSmaET0qO3n40mY5o1so5
+BptMs5pITRNGtFghBMT7oE2sLktiEuP7TfbJUQABH/weaoEqOOC5T9YtRQIDAQAB
+o4ICFTCCAhEwEgYDVR0TAQH/BAgwBgEB/wIBADBwBgNVHSAEaTBnMGUGC2CGSAGG
++EUBBxcDMFYwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9j
+cHMwKgYIKwYBBQUHAgIwHhocaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTAO
+BgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDov
+L2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwNAYDVR0lBC0wKwYIKwYBBQUH
+AwEGCCsGAQUFBwMCBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwNAYIKwYBBQUHAQEE
+KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wNAYDVR0f
+BC0wKzApoCegJYYjaHR0cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy1nNS5jcmww
+KAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFZlcmlTaWduTVBLSS0yLTcwHQYDVR0O
+BBYEFNebfNgioBX33a1fzimbWMO8RgC1MB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ
+80M5+gKvMzEzMA0GCSqGSIb3DQEBBQUAA4IBAQBxtX1zUkrd1000Ky6vlEalSVAC
+T/gvF3DyE9wfIYaqwk98NzzURniuXXhv0bpavBCrWDbFjGIVRWAXIeLVQqh3oVXY
+QwRR9m66SOZdTLdE0z6k1dYzmp8N5tdOlkSVWmzWoxZTDphDzqS4w2Z6BVxiEOgb
+Ett9LnZQ/9/XaxvMisxx+rNAVnwzeneUW/ULU/sOX7xo+68q7jA3eRaTJX9NEP9X
++79uOzMh3nnchhdZLUNkt6Zmh+q8lkYZGoaLb9e3SQBb26O/KZru99MzrqP0nkzK
+XmnUG623kHdq2FlveasB+lXwiiFm5WVu/XzT3x7rfj8GkPsZC9MGAht4Q5mo
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert94[] = {
+  0x30, 0x82, 0x06, 0x29, 0x30, 0x82, 0x05, 0x11, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x64, 0x1b, 0xe8, 0x20, 0xce, 0x02, 0x08, 0x13, 0xf3,
+  0x2d, 0x4d, 0x2d, 0x95, 0xd6, 0x7e, 0x67, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+  0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+  0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+  0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+  0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+  0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+  0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+  0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+  0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+  0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37,
+  0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbc, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+  0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+  0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+  0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+  0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+  0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+  0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x36,
+  0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x56, 0x65, 0x72,
+  0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+  0x33, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+  0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43,
+  0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+  0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+  0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+  0x01, 0x01, 0x00, 0x99, 0xd6, 0x9c, 0x62, 0xf0, 0x15, 0xf4, 0x81, 0x9a,
+  0x41, 0x08, 0x59, 0x8f, 0x13, 0x9d, 0x17, 0xc9, 0x9f, 0x51, 0xdc, 0xda,
+  0xb1, 0x52, 0xef, 0xff, 0xe3, 0x41, 0xdd, 0xe0, 0xdf, 0xc4, 0x28, 0xc6,
+  0xe3, 0xad, 0x79, 0x1f, 0x27, 0x10, 0x98, 0xb8, 0xbb, 0x20, 0x97, 0xc1,
+  0x28, 0x44, 0x41, 0x0f, 0xea, 0xa9, 0xa8, 0x52, 0xcf, 0x4d, 0x4e, 0x1b,
+  0x8b, 0xbb, 0xb5, 0xc4, 0x76, 0xd9, 0xcc, 0x56, 0x06, 0xee, 0xb3, 0x55,
+  0x20, 0x2a, 0xde, 0x15, 0x8d, 0x71, 0xcb, 0x54, 0xc8, 0x6f, 0x17, 0xcd,
+  0x89, 0x00, 0xe4, 0xdc, 0xff, 0xe1, 0xc0, 0x1f, 0x68, 0x71, 0xe9, 0xc7,
+  0x29, 0x2e, 0x7e, 0xbc, 0x3b, 0xfc, 0xe5, 0xbb, 0xab, 0x26, 0x54, 0x8b,
+  0x66, 0x90, 0xcd, 0xf6, 0x92, 0xb9, 0x31, 0x24, 0x80, 0xbc, 0x9e, 0x6c,
+  0xd5, 0xfc, 0x7e, 0xd2, 0xe1, 0x4b, 0x8c, 0xdc, 0x42, 0xfa, 0x44, 0x4b,
+  0x5f, 0xf8, 0x18, 0xb5, 0x2e, 0x30, 0xf4, 0x3d, 0x12, 0x98, 0xd3, 0x62,
+  0x05, 0x73, 0x54, 0xa6, 0x9c, 0xa2, 0x1d, 0xbe, 0x52, 0x83, 0x3a, 0x07,
+  0x46, 0xc4, 0x3b, 0x02, 0x56, 0x21, 0xbf, 0xf2, 0x51, 0x4f, 0xd0, 0xa6,
+  0x99, 0x39, 0xe9, 0xae, 0xa5, 0x3f, 0x89, 0x9b, 0x9c, 0x7d, 0xfe, 0x4d,
+  0x60, 0x07, 0x25, 0x20, 0xf7, 0xbb, 0xd7, 0x69, 0x83, 0x2b, 0x82, 0x93,
+  0x43, 0x37, 0xd9, 0x83, 0x41, 0x1b, 0x6b, 0x0b, 0xab, 0x4a, 0x66, 0x84,
+  0x4f, 0x4a, 0x8e, 0xde, 0x7e, 0x34, 0x99, 0x8e, 0x68, 0xd6, 0xca, 0x39,
+  0x06, 0x9b, 0x4c, 0xb3, 0x9a, 0x48, 0x4d, 0x13, 0x46, 0xb4, 0x58, 0x21,
+  0x04, 0xc4, 0xfb, 0xa0, 0x4d, 0xac, 0x2e, 0x4b, 0x62, 0x12, 0xe3, 0xfb,
+  0x4d, 0xf6, 0xc9, 0x51, 0x00, 0x01, 0x1f, 0xfc, 0x1e, 0x6a, 0x81, 0x2a,
+  0x38, 0xe0, 0xb9, 0x4f, 0xd6, 0x2d, 0x45, 0x02, 0x03, 0x01, 0x00, 0x01,
+  0xa3, 0x82, 0x02, 0x15, 0x30, 0x82, 0x02, 0x11, 0x30, 0x12, 0x06, 0x03,
+  0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+  0xff, 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86,
+  0xf8, 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74,
+  0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65,
+  0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+  0x70, 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x0e,
+  0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+  0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+  0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+  0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+  0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+  0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+  0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+  0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25,
+  0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+  0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06,
+  0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30,
+  0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+  0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76,
+  0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x28, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d,
+  0x30, 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+  0x10, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b,
+  0x49, 0x2d, 0x32, 0x2d, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0xd7, 0x9b, 0x7c, 0xd8, 0x22, 0xa0, 0x15, 0xf7,
+  0xdd, 0xad, 0x5f, 0xce, 0x29, 0x9b, 0x58, 0xc3, 0xbc, 0x46, 0x00, 0xb5,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09,
+  0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x82, 0x01, 0x01, 0x00, 0x71, 0xb5, 0x7d, 0x73, 0x52, 0x4a, 0xdd,
+  0xd7, 0x4d, 0x34, 0x2b, 0x2e, 0xaf, 0x94, 0x46, 0xa5, 0x49, 0x50, 0x02,
+  0x4f, 0xf8, 0x2f, 0x17, 0x70, 0xf2, 0x13, 0xdc, 0x1f, 0x21, 0x86, 0xaa,
+  0xc2, 0x4f, 0x7c, 0x37, 0x3c, 0xd4, 0x46, 0x78, 0xae, 0x5d, 0x78, 0x6f,
+  0xd1, 0xba, 0x5a, 0xbc, 0x10, 0xab, 0x58, 0x36, 0xc5, 0x8c, 0x62, 0x15,
+  0x45, 0x60, 0x17, 0x21, 0xe2, 0xd5, 0x42, 0xa8, 0x77, 0xa1, 0x55, 0xd8,
+  0x43, 0x04, 0x51, 0xf6, 0x6e, 0xba, 0x48, 0xe6, 0x5d, 0x4c, 0xb7, 0x44,
+  0xd3, 0x3e, 0xa4, 0xd5, 0xd6, 0x33, 0x9a, 0x9f, 0x0d, 0xe6, 0xd7, 0x4e,
+  0x96, 0x44, 0x95, 0x5a, 0x6c, 0xd6, 0xa3, 0x16, 0x53, 0x0e, 0x98, 0x43,
+  0xce, 0xa4, 0xb8, 0xc3, 0x66, 0x7a, 0x05, 0x5c, 0x62, 0x10, 0xe8, 0x1b,
+  0x12, 0xdb, 0x7d, 0x2e, 0x76, 0x50, 0xff, 0xdf, 0xd7, 0x6b, 0x1b, 0xcc,
+  0x8a, 0xcc, 0x71, 0xfa, 0xb3, 0x40, 0x56, 0x7c, 0x33, 0x7a, 0x77, 0x94,
+  0x5b, 0xf5, 0x0b, 0x53, 0xfb, 0x0e, 0x5f, 0xbc, 0x68, 0xfb, 0xaf, 0x2a,
+  0xee, 0x30, 0x37, 0x79, 0x16, 0x93, 0x25, 0x7f, 0x4d, 0x10, 0xff, 0x57,
+  0xfb, 0xbf, 0x6e, 0x3b, 0x33, 0x21, 0xde, 0x79, 0xdc, 0x86, 0x17, 0x59,
+  0x2d, 0x43, 0x64, 0xb7, 0xa6, 0x66, 0x87, 0xea, 0xbc, 0x96, 0x46, 0x19,
+  0x1a, 0x86, 0x8b, 0x6f, 0xd7, 0xb7, 0x49, 0x00, 0x5b, 0xdb, 0xa3, 0xbf,
+  0x29, 0x9a, 0xee, 0xf7, 0xd3, 0x33, 0xae, 0xa3, 0xf4, 0x9e, 0x4c, 0xca,
+  0x5e, 0x69, 0xd4, 0x1b, 0xad, 0xb7, 0x90, 0x77, 0x6a, 0xd8, 0x59, 0x6f,
+  0x79, 0xab, 0x01, 0xfa, 0x55, 0xf0, 0x8a, 0x21, 0x66, 0xe5, 0x65, 0x6e,
+  0xfd, 0x7c, 0xd3, 0xdf, 0x1e, 0xeb, 0x7e, 0x3f, 0x06, 0x90, 0xfb, 0x19,
+  0x0b, 0xd3, 0x06, 0x02, 0x1b, 0x78, 0x43, 0x99, 0xa8,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            6e:4f:fa:b3:c5:e6:69:c4:d1:67:c9:92:ab:e8:58:c4
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network
+        Validity
+            Not Before: Mar 25 00:00:00 2009 GMT
+            Not After : Mar 24 23:59:59 2019 GMT
+        Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)09, CN=VeriSign Class 3 Secure Server CA - G2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:d4:56:8f:57:3b:37:28:a6:40:63:d2:95:d5:05:
+                    74:da:b5:19:6a:96:d6:71:57:2f:e2:c0:34:8c:a0:
+                    95:b3:8c:e1:37:24:f3:2e:ed:43:45:05:8e:89:d7:
+                    fa:da:4a:b5:f8:3e:8d:4e:c7:f9:49:50:45:37:40:
+                    9f:74:aa:a0:51:55:61:f1:60:84:89:a5:9e:80:8d:
+                    2f:b0:21:aa:45:82:c4:cf:b4:14:7f:47:15:20:28:
+                    82:b0:68:12:c0:ae:5c:07:d7:f6:59:cc:cb:62:56:
+                    5c:4d:49:ff:26:88:ab:54:51:3a:2f:4a:da:0e:98:
+                    e2:89:72:b9:fc:f7:68:3c:c4:1f:39:7a:cb:17:81:
+                    f3:0c:ad:0f:dc:61:62:1b:10:0b:04:1e:29:18:71:
+                    5e:62:cb:43:de:be:31:ba:71:02:19:4e:26:a9:51:
+                    da:8c:64:69:03:de:9c:fd:7d:fd:7b:61:bc:fc:84:
+                    7c:88:5c:b4:c3:7b:ed:5f:2b:46:12:f1:fd:00:01:
+                    9a:8b:5b:e9:a3:05:2e:8f:2e:5b:de:f3:1b:78:f8:
+                    66:91:08:c0:5e:ce:d5:b0:36:ca:d4:a8:7b:a0:7d:
+                    f9:30:7a:bf:f8:dd:19:51:2b:20:ba:fe:a7:cf:a1:
+                    4e:b0:67:f5:80:aa:2b:83:2e:d2:8e:54:89:8e:1e:
+                    29:0b
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.verisign.com
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.113733.1.7.23.3
+                  CPS: https://www.verisign.com/cps
+                  User Notice:
+                    Explicit Text: https://www.verisign.com/rpa
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl.verisign.com/pca3-g2.crl
+
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            1.3.6.1.5.5.7.1.12: 
+                0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+            X509v3 Subject Alternative Name: 
+                DirName:/CN=Class3CA2048-1-52
+            X509v3 Subject Key Identifier: 
+                A5:EF:0B:11:CE:C0:41:03:A3:4A:65:90:48:B2:1C:E0:57:2D:7D:47
+            X509v3 Authority Key Identifier: 
+                DirName:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network
+                serial:7D:D9:FE:07:CF:A8:1E:B7:10:79:67:FB:A7:89:34:C6
+
+    Signature Algorithm: sha1WithRSAEncryption
+         63:74:2f:3d:53:aa:2f:97:ec:26:11:66:1a:fe:f1:de:41:27:
+         19:d2:7f:d8:c1:1c:f9:e2:38:56:3a:1f:90:ae:39:c5:20:75:
+         ab:f8:6c:2d:67:1f:29:c2:21:d7:14:88:63:4b:b0:9b:27:63:
+         91:f8:f0:a3:01:24:b6:fb:8f:e3:3d:02:0b:6f:54:fe:d4:cc:
+         db:d6:85:bf:7c:95:1e:5e:62:11:c1:d9:09:9c:42:b9:b2:d4:
+         aa:2d:98:3a:23:60:cc:a2:9a:f1:6e:e8:cf:8e:d1:1a:3c:5e:
+         19:c5:d7:9b:35:b0:02:23:24:e5:05:b8:d5:88:e3:e0:fa:b9:
+         f4:5f
+-----BEGIN CERTIFICATE-----
+MIIGLDCCBZWgAwIBAgIQbk/6s8XmacTRZ8mSq+hYxDANBgkqhkiG9w0BAQUFADCB
+wTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTwwOgYDVQQL
+EzNDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzIxOjA4BgNVBAsTMShjKSAxOTk4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1
+dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmswHhcNMDkwMzI1MDAwMDAwWhcNMTkwMzI0MjM1OTU5WjCBtTELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93
+d3cudmVyaXNpZ24uY29tL3JwYSAoYykwOTEvMC0GA1UEAxMmVmVyaVNpZ24gQ2xh
+c3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDUVo9XOzcopkBj0pXVBXTatRlqltZxVy/iwDSMoJWzjOE3JPMu
+7UNFBY6J1/raSrX4Po1Ox/lJUEU3QJ90qqBRVWHxYISJpZ6AjS+wIapFgsTPtBR/
+RxUgKIKwaBLArlwH1/ZZzMtiVlxNSf8miKtUUTovStoOmOKJcrn892g8xB85essX
+gfMMrQ/cYWIbEAsEHikYcV5iy0PevjG6cQIZTiapUdqMZGkD3pz9ff17Ybz8hHyI
+XLTDe+1fK0YS8f0AAZqLW+mjBS6PLlve8xt4+GaRCMBeztWwNsrUqHugffkwer/4
+3RlRKyC6/qfPoU6wZ/WAqiuDLtKOVImOHikLAgMBAAGjggKpMIICpTA0BggrBgEF
+BQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnZlcmlzaWduLmNvbTAS
+BgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4RQEHFwMwVjAo
+BggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAqBggrBgEF
+BQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMtZzIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYw
+ITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9n
+by52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjApBgNVHREEIjAgpB4wHDEaMBgGA1UE
+AxMRQ2xhc3MzQ0EyMDQ4LTEtNTIwHQYDVR0OBBYEFKXvCxHOwEEDo0plkEiyHOBX
+LX1HMIHnBgNVHSMEgd8wgdyhgcekgcQwgcExCzAJBgNVBAYTAlVTMRcwFQYDVQQK
+Ew5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5
+OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrghB92f4Hz6getxB5Z/uniTTGMA0G
+CSqGSIb3DQEBBQUAA4GBAGN0Lz1Tqi+X7CYRZhr+8d5BJxnSf9jBHPniOFY6H5Cu
+OcUgdav4bC1nHynCIdcUiGNLsJsnY5H48KMBJLb7j+M9AgtvVP7UzNvWhb98lR5e
+YhHB2QmcQrmy1KotmDojYMyimvFu6M+O0Ro8XhnF15s1sAIjJOUFuNWI4+D6ufRf
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert95[] = {
+  0x30, 0x82, 0x06, 0x2c, 0x30, 0x82, 0x05, 0x95, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x6e, 0x4f, 0xfa, 0xb3, 0xc5, 0xe6, 0x69, 0xc4, 0xd1,
+  0x67, 0xc9, 0x92, 0xab, 0xe8, 0x58, 0xc4, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+  0xc1, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x33, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75,
+  0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
+  0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+  0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+  0x20, 0x2d, 0x20, 0x47, 0x32, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38,
+  0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+  0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+  0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+  0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+  0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f,
+  0x72, 0x6b, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x33, 0x32, 0x35,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x30,
+  0x33, 0x32, 0x34, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81,
+  0xb5, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+  0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+  0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+  0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+  0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54,
+  0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20,
+  0x61, 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30,
+  0x39, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+  0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61,
+  0x73, 0x73, 0x20, 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+  0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd4,
+  0x56, 0x8f, 0x57, 0x3b, 0x37, 0x28, 0xa6, 0x40, 0x63, 0xd2, 0x95, 0xd5,
+  0x05, 0x74, 0xda, 0xb5, 0x19, 0x6a, 0x96, 0xd6, 0x71, 0x57, 0x2f, 0xe2,
+  0xc0, 0x34, 0x8c, 0xa0, 0x95, 0xb3, 0x8c, 0xe1, 0x37, 0x24, 0xf3, 0x2e,
+  0xed, 0x43, 0x45, 0x05, 0x8e, 0x89, 0xd7, 0xfa, 0xda, 0x4a, 0xb5, 0xf8,
+  0x3e, 0x8d, 0x4e, 0xc7, 0xf9, 0x49, 0x50, 0x45, 0x37, 0x40, 0x9f, 0x74,
+  0xaa, 0xa0, 0x51, 0x55, 0x61, 0xf1, 0x60, 0x84, 0x89, 0xa5, 0x9e, 0x80,
+  0x8d, 0x2f, 0xb0, 0x21, 0xaa, 0x45, 0x82, 0xc4, 0xcf, 0xb4, 0x14, 0x7f,
+  0x47, 0x15, 0x20, 0x28, 0x82, 0xb0, 0x68, 0x12, 0xc0, 0xae, 0x5c, 0x07,
+  0xd7, 0xf6, 0x59, 0xcc, 0xcb, 0x62, 0x56, 0x5c, 0x4d, 0x49, 0xff, 0x26,
+  0x88, 0xab, 0x54, 0x51, 0x3a, 0x2f, 0x4a, 0xda, 0x0e, 0x98, 0xe2, 0x89,
+  0x72, 0xb9, 0xfc, 0xf7, 0x68, 0x3c, 0xc4, 0x1f, 0x39, 0x7a, 0xcb, 0x17,
+  0x81, 0xf3, 0x0c, 0xad, 0x0f, 0xdc, 0x61, 0x62, 0x1b, 0x10, 0x0b, 0x04,
+  0x1e, 0x29, 0x18, 0x71, 0x5e, 0x62, 0xcb, 0x43, 0xde, 0xbe, 0x31, 0xba,
+  0x71, 0x02, 0x19, 0x4e, 0x26, 0xa9, 0x51, 0xda, 0x8c, 0x64, 0x69, 0x03,
+  0xde, 0x9c, 0xfd, 0x7d, 0xfd, 0x7b, 0x61, 0xbc, 0xfc, 0x84, 0x7c, 0x88,
+  0x5c, 0xb4, 0xc3, 0x7b, 0xed, 0x5f, 0x2b, 0x46, 0x12, 0xf1, 0xfd, 0x00,
+  0x01, 0x9a, 0x8b, 0x5b, 0xe9, 0xa3, 0x05, 0x2e, 0x8f, 0x2e, 0x5b, 0xde,
+  0xf3, 0x1b, 0x78, 0xf8, 0x66, 0x91, 0x08, 0xc0, 0x5e, 0xce, 0xd5, 0xb0,
+  0x36, 0xca, 0xd4, 0xa8, 0x7b, 0xa0, 0x7d, 0xf9, 0x30, 0x7a, 0xbf, 0xf8,
+  0xdd, 0x19, 0x51, 0x2b, 0x20, 0xba, 0xfe, 0xa7, 0xcf, 0xa1, 0x4e, 0xb0,
+  0x67, 0xf5, 0x80, 0xaa, 0x2b, 0x83, 0x2e, 0xd2, 0x8e, 0x54, 0x89, 0x8e,
+  0x1e, 0x29, 0x0b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x02, 0xa9,
+  0x30, 0x82, 0x02, 0xa5, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65,
+  0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12,
+  0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06,
+  0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+  0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+  0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x63, 0x70, 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70,
+  0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69,
+  0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61,
+  0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30,
+  0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+  0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d,
+  0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61,
+  0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55,
+  0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30,
+  0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+  0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3,
+  0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25,
+  0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67,
+  0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69,
+  0x66, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20,
+  0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
+  0x03, 0x13, 0x11, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x33, 0x43, 0x41, 0x32,
+  0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d, 0x35, 0x32, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xa5, 0xef, 0x0b, 0x11, 0xce,
+  0xc0, 0x41, 0x03, 0xa3, 0x4a, 0x65, 0x90, 0x48, 0xb2, 0x1c, 0xe0, 0x57,
+  0x2d, 0x7d, 0x47, 0x30, 0x81, 0xe7, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x81, 0xdf, 0x30, 0x81, 0xdc, 0xa1, 0x81, 0xc7, 0xa4, 0x81, 0xc4, 0x30,
+  0x81, 0xc1, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+  0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a,
+  0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04,
+  0x0b, 0x13, 0x33, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50,
+  0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72,
+  0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+  0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39,
+  0x38, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20,
+  0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+  0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+  0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77,
+  0x6f, 0x72, 0x6b, 0x82, 0x10, 0x7d, 0xd9, 0xfe, 0x07, 0xcf, 0xa8, 0x1e,
+  0xb7, 0x10, 0x79, 0x67, 0xfb, 0xa7, 0x89, 0x34, 0xc6, 0x30, 0x0d, 0x06,
+  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+  0x03, 0x81, 0x81, 0x00, 0x63, 0x74, 0x2f, 0x3d, 0x53, 0xaa, 0x2f, 0x97,
+  0xec, 0x26, 0x11, 0x66, 0x1a, 0xfe, 0xf1, 0xde, 0x41, 0x27, 0x19, 0xd2,
+  0x7f, 0xd8, 0xc1, 0x1c, 0xf9, 0xe2, 0x38, 0x56, 0x3a, 0x1f, 0x90, 0xae,
+  0x39, 0xc5, 0x20, 0x75, 0xab, 0xf8, 0x6c, 0x2d, 0x67, 0x1f, 0x29, 0xc2,
+  0x21, 0xd7, 0x14, 0x88, 0x63, 0x4b, 0xb0, 0x9b, 0x27, 0x63, 0x91, 0xf8,
+  0xf0, 0xa3, 0x01, 0x24, 0xb6, 0xfb, 0x8f, 0xe3, 0x3d, 0x02, 0x0b, 0x6f,
+  0x54, 0xfe, 0xd4, 0xcc, 0xdb, 0xd6, 0x85, 0xbf, 0x7c, 0x95, 0x1e, 0x5e,
+  0x62, 0x11, 0xc1, 0xd9, 0x09, 0x9c, 0x42, 0xb9, 0xb2, 0xd4, 0xaa, 0x2d,
+  0x98, 0x3a, 0x23, 0x60, 0xcc, 0xa2, 0x9a, 0xf1, 0x6e, 0xe8, 0xcf, 0x8e,
+  0xd1, 0x1a, 0x3c, 0x5e, 0x19, 0xc5, 0xd7, 0x9b, 0x35, 0xb0, 0x02, 0x23,
+  0x24, 0xe5, 0x05, 0xb8, 0xd5, 0x88, 0xe3, 0xe0, 0xfa, 0xb9, 0xf4, 0x5f,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 24 (0x18)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+        Validity
+            Not Before: Oct 24 20:54:17 2007 GMT
+            Not After : Oct 24 20:54:17 2017 GMT
+        Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 1 Primary Intermediate Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b6:89:c6:ac:ef:09:52:78:07:ac:92:63:d0:f4:
+                    44:18:18:84:80:56:1f:91:ae:e1:87:fa:32:50:b4:
+                    d3:47:06:f0:e6:07:5f:70:0e:10:f7:1d:c0:ce:10:
+                    36:34:85:5a:0f:92:ac:83:c6:ac:58:52:3f:ba:38:
+                    e8:fc:e7:a7:24:e2:40:a6:08:76:c0:92:6e:9e:2a:
+                    6d:4d:3f:6e:61:20:0a:db:59:de:d2:7d:63:b3:3e:
+                    46:fe:fa:21:51:18:d7:cd:30:a6:ed:07:6e:3b:70:
+                    87:b4:f9:fa:eb:ee:82:3c:05:6f:92:f7:a4:dc:0a:
+                    30:1e:93:73:fe:07:ca:d7:5f:80:9d:22:58:52:ae:
+                    06:da:8b:87:23:69:b0:e4:2a:d8:ea:83:d2:bd:f3:
+                    71:db:70:5a:28:0f:af:5a:38:70:45:12:3f:30:4d:
+                    cd:3b:af:17:e5:0f:cb:a0:a9:5d:48:aa:b1:61:50:
+                    cb:34:cd:3c:5c:c3:0b:e8:10:c0:8c:9b:f0:03:03:
+                    62:fe:b2:6c:3e:72:0e:ee:1c:43:2a:c9:48:0e:57:
+                    39:c4:31:21:c8:10:c1:2c:87:fe:54:95:52:1f:52:
+                    3c:31:12:9b:7f:e7:c0:a0:a5:59:d5:e2:8f:3e:f0:
+                    d5:a8:e1:d7:70:31:a9:c4:b3:cf:af:6d:53:2f:06:
+                    f4:a7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                EB:42:34:D0:98:B0:AB:9F:F4:1B:6B:08:F7:CC:64:2E:EF:0E:2C:45
+            X509v3 Authority Key Identifier: 
+                keyid:4E:0B:EF:1A:A4:40:5B:A5:17:69:87:30:CA:34:68:43:D0:41:AE:F2
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.startssl.com/ca
+                CA Issuers - URI:http://www.startssl.com/sfsca.crt
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.startssl.com/sfsca.crl
+
+                Full Name:
+                  URI:http://crl.startssl.com/sfsca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.23223.1.2.1
+                  CPS: http://www.startssl.com/policy.pdf
+                  CPS: http://www.startssl.com/intermediate.pdf
+
+    Signature Algorithm: sha1WithRSAEncryption
+         21:09:49:3e:a5:88:6e:e0:0b:8b:48:da:31:4d:8f:f7:56:57:
+         a2:e1:d3:62:57:e9:b5:56:f3:85:45:75:3b:e5:50:1f:04:8b:
+         e6:a0:5a:3e:e7:00:ae:85:d0:fb:ff:20:03:64:cb:ad:02:e1:
+         c6:91:72:f8:a3:4d:d6:de:e8:cc:3f:a1:8a:a2:e3:7c:37:a7:
+         c6:4f:8f:35:d6:f4:d6:6e:06:7b:dd:21:d9:cf:56:ff:cb:30:
+         22:49:fe:89:04:f3:85:e5:aa:f1:e7:1f:e8:75:90:4d:dd:f9:
+         46:f7:42:34:f7:45:58:0c:11:0d:84:b0:c6:da:5d:3e:f9:01:
+         9e:e7:e1:da:55:95:be:74:1c:7b:fc:4d:14:4f:ac:7e:55:47:
+         7d:7b:f4:a5:0d:49:1e:95:e8:f7:12:c1:cc:ff:76:a6:25:47:
+         d0:f3:75:35:be:97:b7:58:16:eb:aa:5c:78:6f:ec:53:30:af:
+         ea:04:4d:cc:a9:02:e3:f0:b6:04:12:f6:30:b1:11:3d:90:4e:
+         56:64:d7:dc:3c:43:5f:73:39:ef:4b:af:87:eb:f6:fe:68:88:
+         44:72:ea:d2:07:c6:69:b0:c1:a1:8b:ef:17:49:d7:61:b1:45:
+         48:5f:3b:20:21:e9:5b:b2:cc:f4:d7:e9:31:f5:0b:15:61:3b:
+         7a:94:e3:eb:d9:bc:7f:94:ae:6a:e3:62:62:96:a8:64:7c:b8:
+         87:f3:99:32:7e:92:a2:52:be:bb:f8:65:cf:c9:f2:30:fc:8b:
+         c1:c2:a6:96:d7:5f:89:e1:5c:34:80:f5:8f:47:07:2f:b4:91:
+         bf:b1:a2:7e:5f:4b:5a:d0:5b:9f:24:86:05:51:5a:69:03:65:
+         43:49:71:c5:e0:6f:94:34:6b:f6:1b:d8:a9:b0:4c:7e:53:eb:
+         8f:48:df:ca:33:b5:48:fa:36:4a:1a:53:a6:33:0c:d0:89:cd:
+         49:15:cd:89:31:3c:90:c0:72:d7:65:4b:52:35:8a:46:11:44:
+         b9:3d:8e:28:65:a6:3e:79:9e:5c:08:44:29:ad:b0:35:11:2e:
+         21:4e:b8:d2:e7:10:3e:5d:84:83:b3:c3:c2:e4:d2:c6:fd:09:
+         4b:74:09:dd:f1:b3:d3:19:3e:80:0d:a2:0b:19:f0:38:e7:c5:
+         c2:af:e2:23:db:61:e2:9d:5c:6e:20:89:49:2e:23:6a:b2:62:
+         c1:45:b4:9f:af:8b:a7:f1:22:3b:f8:7d:e2:90:d0:7a:19:fb:
+         4a:4c:e3:d2:7d:5f:4a:83:03:ed:27:d6:23:9e:6b:8d:b4:59:
+         a2:d9:ef:6c:82:29:dd:75:19:3c:3f:4c:10:8d:ef:bb:75:27:
+         d2:ae:83:a7:a8:ce:5b:a7
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE
+gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA
+pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv
+kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/
+ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5
+xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp
+tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen
+xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw
+xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X
+t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI
+RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi
+YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L
+WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN
+SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD
+wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L
+p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un
+0q6Dp6jOW6c=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert96[] = {
+  0x30, 0x82, 0x06, 0x34, 0x30, 0x82, 0x04, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x01, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16,
+  0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61,
+  0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b,
+  0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+  0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43,
+  0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x30, 0x32, 0x34,
+  0x32, 0x30, 0x35, 0x34, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x31,
+  0x30, 0x32, 0x34, 0x32, 0x30, 0x35, 0x34, 0x31, 0x37, 0x5a, 0x30, 0x81,
+  0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74,
+  0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69,
+  0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31,
+  0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x53, 0x74,
+  0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73,
+  0x20, 0x31, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x49,
+  0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x89, 0xc6, 0xac, 0xef, 0x09,
+  0x52, 0x78, 0x07, 0xac, 0x92, 0x63, 0xd0, 0xf4, 0x44, 0x18, 0x18, 0x84,
+  0x80, 0x56, 0x1f, 0x91, 0xae, 0xe1, 0x87, 0xfa, 0x32, 0x50, 0xb4, 0xd3,
+  0x47, 0x06, 0xf0, 0xe6, 0x07, 0x5f, 0x70, 0x0e, 0x10, 0xf7, 0x1d, 0xc0,
+  0xce, 0x10, 0x36, 0x34, 0x85, 0x5a, 0x0f, 0x92, 0xac, 0x83, 0xc6, 0xac,
+  0x58, 0x52, 0x3f, 0xba, 0x38, 0xe8, 0xfc, 0xe7, 0xa7, 0x24, 0xe2, 0x40,
+  0xa6, 0x08, 0x76, 0xc0, 0x92, 0x6e, 0x9e, 0x2a, 0x6d, 0x4d, 0x3f, 0x6e,
+  0x61, 0x20, 0x0a, 0xdb, 0x59, 0xde, 0xd2, 0x7d, 0x63, 0xb3, 0x3e, 0x46,
+  0xfe, 0xfa, 0x21, 0x51, 0x18, 0xd7, 0xcd, 0x30, 0xa6, 0xed, 0x07, 0x6e,
+  0x3b, 0x70, 0x87, 0xb4, 0xf9, 0xfa, 0xeb, 0xee, 0x82, 0x3c, 0x05, 0x6f,
+  0x92, 0xf7, 0xa4, 0xdc, 0x0a, 0x30, 0x1e, 0x93, 0x73, 0xfe, 0x07, 0xca,
+  0xd7, 0x5f, 0x80, 0x9d, 0x22, 0x58, 0x52, 0xae, 0x06, 0xda, 0x8b, 0x87,
+  0x23, 0x69, 0xb0, 0xe4, 0x2a, 0xd8, 0xea, 0x83, 0xd2, 0xbd, 0xf3, 0x71,
+  0xdb, 0x70, 0x5a, 0x28, 0x0f, 0xaf, 0x5a, 0x38, 0x70, 0x45, 0x12, 0x3f,
+  0x30, 0x4d, 0xcd, 0x3b, 0xaf, 0x17, 0xe5, 0x0f, 0xcb, 0xa0, 0xa9, 0x5d,
+  0x48, 0xaa, 0xb1, 0x61, 0x50, 0xcb, 0x34, 0xcd, 0x3c, 0x5c, 0xc3, 0x0b,
+  0xe8, 0x10, 0xc0, 0x8c, 0x9b, 0xf0, 0x03, 0x03, 0x62, 0xfe, 0xb2, 0x6c,
+  0x3e, 0x72, 0x0e, 0xee, 0x1c, 0x43, 0x2a, 0xc9, 0x48, 0x0e, 0x57, 0x39,
+  0xc4, 0x31, 0x21, 0xc8, 0x10, 0xc1, 0x2c, 0x87, 0xfe, 0x54, 0x95, 0x52,
+  0x1f, 0x52, 0x3c, 0x31, 0x12, 0x9b, 0x7f, 0xe7, 0xc0, 0xa0, 0xa5, 0x59,
+  0xd5, 0xe2, 0x8f, 0x3e, 0xf0, 0xd5, 0xa8, 0xe1, 0xd7, 0x70, 0x31, 0xa9,
+  0xc4, 0xb3, 0xcf, 0xaf, 0x6d, 0x53, 0x2f, 0x06, 0xf4, 0xa7, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xad, 0x30, 0x82, 0x01, 0xa9, 0x30,
+  0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+  0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xeb, 0x42, 0x34, 0xd0, 0x98,
+  0xb0, 0xab, 0x9f, 0xf4, 0x1b, 0x6b, 0x08, 0xf7, 0xcc, 0x64, 0x2e, 0xef,
+  0x0e, 0x2c, 0x45, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+  0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, 0xa4, 0x40, 0x5b, 0xa5,
+  0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, 0xd0, 0x41, 0xae, 0xf2,
+  0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x5a, 0x30, 0x58, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73,
+  0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x30, 0x2d, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x74,
+  0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73,
+  0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x5b, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x54, 0x30, 0x52, 0x30, 0x27, 0xa0, 0x25, 0xa0,
+  0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+  0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74,
+  0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63,
+  0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x79, 0x30, 0x77, 0x30, 0x75, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x81, 0xb5, 0x37, 0x01, 0x02, 0x01, 0x30, 0x66, 0x30, 0x2e,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x22,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x64, 0x66, 0x30, 0x34,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x28,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65,
+  0x2e, 0x70, 0x64, 0x66, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00,
+  0x21, 0x09, 0x49, 0x3e, 0xa5, 0x88, 0x6e, 0xe0, 0x0b, 0x8b, 0x48, 0xda,
+  0x31, 0x4d, 0x8f, 0xf7, 0x56, 0x57, 0xa2, 0xe1, 0xd3, 0x62, 0x57, 0xe9,
+  0xb5, 0x56, 0xf3, 0x85, 0x45, 0x75, 0x3b, 0xe5, 0x50, 0x1f, 0x04, 0x8b,
+  0xe6, 0xa0, 0x5a, 0x3e, 0xe7, 0x00, 0xae, 0x85, 0xd0, 0xfb, 0xff, 0x20,
+  0x03, 0x64, 0xcb, 0xad, 0x02, 0xe1, 0xc6, 0x91, 0x72, 0xf8, 0xa3, 0x4d,
+  0xd6, 0xde, 0xe8, 0xcc, 0x3f, 0xa1, 0x8a, 0xa2, 0xe3, 0x7c, 0x37, 0xa7,
+  0xc6, 0x4f, 0x8f, 0x35, 0xd6, 0xf4, 0xd6, 0x6e, 0x06, 0x7b, 0xdd, 0x21,
+  0xd9, 0xcf, 0x56, 0xff, 0xcb, 0x30, 0x22, 0x49, 0xfe, 0x89, 0x04, 0xf3,
+  0x85, 0xe5, 0xaa, 0xf1, 0xe7, 0x1f, 0xe8, 0x75, 0x90, 0x4d, 0xdd, 0xf9,
+  0x46, 0xf7, 0x42, 0x34, 0xf7, 0x45, 0x58, 0x0c, 0x11, 0x0d, 0x84, 0xb0,
+  0xc6, 0xda, 0x5d, 0x3e, 0xf9, 0x01, 0x9e, 0xe7, 0xe1, 0xda, 0x55, 0x95,
+  0xbe, 0x74, 0x1c, 0x7b, 0xfc, 0x4d, 0x14, 0x4f, 0xac, 0x7e, 0x55, 0x47,
+  0x7d, 0x7b, 0xf4, 0xa5, 0x0d, 0x49, 0x1e, 0x95, 0xe8, 0xf7, 0x12, 0xc1,
+  0xcc, 0xff, 0x76, 0xa6, 0x25, 0x47, 0xd0, 0xf3, 0x75, 0x35, 0xbe, 0x97,
+  0xb7, 0x58, 0x16, 0xeb, 0xaa, 0x5c, 0x78, 0x6f, 0xec, 0x53, 0x30, 0xaf,
+  0xea, 0x04, 0x4d, 0xcc, 0xa9, 0x02, 0xe3, 0xf0, 0xb6, 0x04, 0x12, 0xf6,
+  0x30, 0xb1, 0x11, 0x3d, 0x90, 0x4e, 0x56, 0x64, 0xd7, 0xdc, 0x3c, 0x43,
+  0x5f, 0x73, 0x39, 0xef, 0x4b, 0xaf, 0x87, 0xeb, 0xf6, 0xfe, 0x68, 0x88,
+  0x44, 0x72, 0xea, 0xd2, 0x07, 0xc6, 0x69, 0xb0, 0xc1, 0xa1, 0x8b, 0xef,
+  0x17, 0x49, 0xd7, 0x61, 0xb1, 0x45, 0x48, 0x5f, 0x3b, 0x20, 0x21, 0xe9,
+  0x5b, 0xb2, 0xcc, 0xf4, 0xd7, 0xe9, 0x31, 0xf5, 0x0b, 0x15, 0x61, 0x3b,
+  0x7a, 0x94, 0xe3, 0xeb, 0xd9, 0xbc, 0x7f, 0x94, 0xae, 0x6a, 0xe3, 0x62,
+  0x62, 0x96, 0xa8, 0x64, 0x7c, 0xb8, 0x87, 0xf3, 0x99, 0x32, 0x7e, 0x92,
+  0xa2, 0x52, 0xbe, 0xbb, 0xf8, 0x65, 0xcf, 0xc9, 0xf2, 0x30, 0xfc, 0x8b,
+  0xc1, 0xc2, 0xa6, 0x96, 0xd7, 0x5f, 0x89, 0xe1, 0x5c, 0x34, 0x80, 0xf5,
+  0x8f, 0x47, 0x07, 0x2f, 0xb4, 0x91, 0xbf, 0xb1, 0xa2, 0x7e, 0x5f, 0x4b,
+  0x5a, 0xd0, 0x5b, 0x9f, 0x24, 0x86, 0x05, 0x51, 0x5a, 0x69, 0x03, 0x65,
+  0x43, 0x49, 0x71, 0xc5, 0xe0, 0x6f, 0x94, 0x34, 0x6b, 0xf6, 0x1b, 0xd8,
+  0xa9, 0xb0, 0x4c, 0x7e, 0x53, 0xeb, 0x8f, 0x48, 0xdf, 0xca, 0x33, 0xb5,
+  0x48, 0xfa, 0x36, 0x4a, 0x1a, 0x53, 0xa6, 0x33, 0x0c, 0xd0, 0x89, 0xcd,
+  0x49, 0x15, 0xcd, 0x89, 0x31, 0x3c, 0x90, 0xc0, 0x72, 0xd7, 0x65, 0x4b,
+  0x52, 0x35, 0x8a, 0x46, 0x11, 0x44, 0xb9, 0x3d, 0x8e, 0x28, 0x65, 0xa6,
+  0x3e, 0x79, 0x9e, 0x5c, 0x08, 0x44, 0x29, 0xad, 0xb0, 0x35, 0x11, 0x2e,
+  0x21, 0x4e, 0xb8, 0xd2, 0xe7, 0x10, 0x3e, 0x5d, 0x84, 0x83, 0xb3, 0xc3,
+  0xc2, 0xe4, 0xd2, 0xc6, 0xfd, 0x09, 0x4b, 0x74, 0x09, 0xdd, 0xf1, 0xb3,
+  0xd3, 0x19, 0x3e, 0x80, 0x0d, 0xa2, 0x0b, 0x19, 0xf0, 0x38, 0xe7, 0xc5,
+  0xc2, 0xaf, 0xe2, 0x23, 0xdb, 0x61, 0xe2, 0x9d, 0x5c, 0x6e, 0x20, 0x89,
+  0x49, 0x2e, 0x23, 0x6a, 0xb2, 0x62, 0xc1, 0x45, 0xb4, 0x9f, 0xaf, 0x8b,
+  0xa7, 0xf1, 0x22, 0x3b, 0xf8, 0x7d, 0xe2, 0x90, 0xd0, 0x7a, 0x19, 0xfb,
+  0x4a, 0x4c, 0xe3, 0xd2, 0x7d, 0x5f, 0x4a, 0x83, 0x03, 0xed, 0x27, 0xd6,
+  0x23, 0x9e, 0x6b, 0x8d, 0xb4, 0x59, 0xa2, 0xd9, 0xef, 0x6c, 0x82, 0x29,
+  0xdd, 0x75, 0x19, 0x3c, 0x3f, 0x4c, 0x10, 0x8d, 0xef, 0xbb, 0x75, 0x27,
+  0xd2, 0xae, 0x83, 0xa7, 0xa8, 0xce, 0x5b, 0xa7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 26 (0x1a)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+        Validity
+            Not Before: Oct 24 20:57:09 2007 GMT
+            Not After : Oct 24 20:57:09 2017 GMT
+        Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 2 Primary Intermediate Server CA
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:e2:4f:39:2f:a1:8c:9a:85:ad:08:0e:08:3e:57:
+                    f2:88:01:21:1b:94:a9:6c:e2:b8:db:aa:19:18:46:
+                    3a:52:a1:f5:0f:f4:6e:8c:ea:96:8c:96:87:79:13:
+                    40:51:2f:22:f2:0c:8b:87:0f:65:df:71:74:34:43:
+                    55:b1:35:09:9b:d9:bc:1f:fa:eb:42:d0:97:40:72:
+                    b7:43:96:3d:ba:96:9d:5d:50:02:1c:9b:91:8d:9c:
+                    c0:ac:d7:bb:2f:17:d7:cb:3e:82:9d:73:eb:07:42:
+                    92:b2:cd:64:b3:74:55:1b:b4:4b:86:21:2c:f7:78:
+                    87:32:e0:16:e4:da:bd:4c:95:ea:a4:0a:7e:b6:0a:
+                    0d:2e:8a:cf:55:ab:c3:e5:dd:41:8a:4e:e6:6f:65:
+                    6c:b2:40:cf:17:5d:b9:c3:6a:0b:27:11:84:77:61:
+                    f6:c2:7c:ed:c0:8d:78:14:18:99:81:99:75:63:b7:
+                    e8:53:d3:ba:61:e9:0e:fa:a2:30:f3:46:a2:b9:c9:
+                    1f:6c:80:5a:40:ac:27:ed:48:47:33:b0:54:c6:46:
+                    1a:f3:35:61:c1:02:29:90:54:7e:64:4d:c4:30:52:
+                    02:82:d7:df:ce:21:6e:18:91:d7:b8:ab:8c:27:17:
+                    b5:f0:a3:01:2f:8e:d2:2e:87:3a:3d:b4:29:67:8a:
+                    c4:03
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Key Usage: critical
+                Certificate Sign, CRL Sign
+            X509v3 Subject Key Identifier: 
+                11:DB:23:45:FD:54:CC:6A:71:6F:84:8A:03:D7:BE:F7:01:2F:26:86
+            X509v3 Authority Key Identifier: 
+                keyid:4E:0B:EF:1A:A4:40:5B:A5:17:69:87:30:CA:34:68:43:D0:41:AE:F2
+
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.startssl.com/ca
+                CA Issuers - URI:http://www.startssl.com/sfsca.crt
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.startssl.com/sfsca.crl
+
+                Full Name:
+                  URI:http://crl.startssl.com/sfsca.crl
+
+            X509v3 Certificate Policies: 
+                Policy: 1.3.6.1.4.1.23223.1.2.1
+                  CPS: http://www.startssl.com/policy.pdf
+                  CPS: http://www.startssl.com/intermediate.pdf
+
+    Signature Algorithm: sha1WithRSAEncryption
+         9d:07:e1:ee:90:76:31:67:16:45:70:8c:cb:84:8b:4b:57:68:
+         44:a5:89:c1:f2:7e:cb:28:8b:f5:e7:70:77:d5:b6:f4:0b:21:
+         60:a5:a1:74:73:24:22:80:d6:d8:ba:8d:a2:62:5d:09:35:42:
+         29:fb:39:63:45:0b:a4:b0:38:1a:68:f4:95:13:cc:e0:43:94:
+         ec:eb:39:1a:ec:57:29:d9:99:6d:f5:84:cd:8e:73:ae:c9:dc:
+         6a:fa:9e:9d:16:64:93:08:c7:1c:c2:89:54:9e:77:80:90:f6:
+         b9:29:76:eb:13:67:48:59:f8:2e:3a:31:b8:c9:d3:88:e5:5f:
+         4e:d2:19:3d:43:8e:d7:92:ff:cf:38:b6:e1:5b:8a:53:1d:ce:
+         ac:b4:76:2f:d8:f7:40:63:d5:ee:69:f3:45:7d:a0:62:c1:61:
+         c3:75:ed:b2:7b:4d:ac:21:27:30:4e:59:46:6a:93:17:ca:c8:
+         39:2d:01:73:65:5b:e9:41:9b:11:17:9c:c8:c8:4a:ef:a1:76:
+         60:2d:ae:93:ff:0c:d5:33:13:9f:4f:13:ce:dd:86:f1:fc:f8:
+         35:54:15:a8:5b:e7:85:7e:fa:37:09:ff:8b:b8:31:49:9e:0d:
+         6e:de:b4:d2:12:2d:b8:ed:c8:c3:f1:b6:42:a0:4c:97:79:df:
+         fe:c3:a3:9f:a1:f4:6d:2c:84:77:a4:a2:05:e1:17:ff:31:dd:
+         9a:f3:b8:7a:c3:52:c2:11:11:b7:50:31:8a:7f:cc:e7:5a:89:
+         cc:f7:86:9a:61:92:4f:2f:94:b6:98:c7:78:e0:62:4b:43:7d:
+         3c:de:d6:9a:b4:10:a1:40:9c:4b:2a:dc:b8:d0:d4:9e:fd:f1:
+         84:78:1b:0e:57:8f:69:54:42:68:7b:ea:a0:ef:75:0f:07:a2:
+         8c:73:99:ab:55:f5:07:09:d2:af:38:03:6a:90:03:0c:2f:8f:
+         e2:e8:43:c2:31:e9:6f:ad:87:e5:8d:bd:4e:2c:89:4b:51:e6:
+         9c:4c:54:76:c0:12:81:53:9b:ec:a0:fc:2c:9c:da:18:95:6e:
+         1e:38:26:42:27:78:60:08:df:7f:6d:32:e8:d8:c0:6f:1f:eb:
+         26:75:9f:93:fc:7b:1b:fe:35:90:dc:53:a3:07:a6:3f:83:55:
+         0a:2b:4e:62:82:25:ce:66:30:5d:2c:e0:f9:19:1b:75:b9:9d:
+         98:56:a6:83:27:7a:d1:8f:8d:59:93:fc:3f:73:d7:2e:b4:2c:
+         95:d8:8b:f7:c9:7e:c7:fc:9d:ac:72:04:1f:d2:cc:17:f4:ed:
+         34:60:9b:9e:4a:97:04:fe:dd:72:0e:57:54:51:06:70:4d:ef:
+         aa:1c:a4:82:e0:33:c7:f4
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh
+G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8
+H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL
+hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE
+d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa
+8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAnQfh7pB2MWcWRXCMy4SLS1doRKWJwfJ+
+yyiL9edwd9W29AshYKWhdHMkIoDW2LqNomJdCTVCKfs5Y0ULpLA4Gmj0lRPM4EOU
+7Os5GuxXKdmZbfWEzY5zrsncavqenRZkkwjHHMKJVJ53gJD2uSl26xNnSFn4Ljox
+uMnTiOVfTtIZPUOO15L/zzi24VuKUx3OrLR2L9j3QGPV7mnzRX2gYsFhw3XtsntN
+rCEnME5ZRmqTF8rIOS0Bc2Vb6UGbERecyMhK76F2YC2uk/8M1TMTn08Tzt2G8fz4
+NVQVqFvnhX76Nwn/i7gxSZ4Nbt600hItuO3Iw/G2QqBMl3nf/sOjn6H0bSyEd6Si
+BeEX/zHdmvO4esNSwhERt1Axin/M51qJzPeGmmGSTy+UtpjHeOBiS0N9PN7WmrQQ
+oUCcSyrcuNDUnv3xhHgbDlePaVRCaHvqoO91DweijHOZq1X1BwnSrzgDapADDC+P
+4uhDwjHpb62H5Y29TiyJS1HmnExUdsASgVOb7KD8LJzaGJVuHjgmQid4YAjff20y
+6NjAbx/rJnWfk/x7G/41kNxTowemP4NVCitOYoIlzmYwXSzg+RkbdbmdmFamgyd6
+0Y+NWZP8P3PXLrQsldiL98l+x/ydrHIEH9LMF/TtNGCbnkqXBP7dcg5XVFEGcE3v
+qhykguAzx/Q=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert97[] = {
+  0x30, 0x82, 0x06, 0x34, 0x30, 0x82, 0x04, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x01, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30,
+  0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16,
+  0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61,
+  0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b,
+  0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63,
+  0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20,
+  0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+  0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06,
+  0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43,
+  0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+  0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+  0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x30, 0x32, 0x34,
+  0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x31,
+  0x30, 0x32, 0x34, 0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x30, 0x81,
+  0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+  0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+  0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74,
+  0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+  0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69,
+  0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+  0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31,
+  0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x53, 0x74,
+  0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73,
+  0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x49,
+  0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20,
+  0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+  0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+  0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+  0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0x4f, 0x39, 0x2f, 0xa1, 0x8c,
+  0x9a, 0x85, 0xad, 0x08, 0x0e, 0x08, 0x3e, 0x57, 0xf2, 0x88, 0x01, 0x21,
+  0x1b, 0x94, 0xa9, 0x6c, 0xe2, 0xb8, 0xdb, 0xaa, 0x19, 0x18, 0x46, 0x3a,
+  0x52, 0xa1, 0xf5, 0x0f, 0xf4, 0x6e, 0x8c, 0xea, 0x96, 0x8c, 0x96, 0x87,
+  0x79, 0x13, 0x40, 0x51, 0x2f, 0x22, 0xf2, 0x0c, 0x8b, 0x87, 0x0f, 0x65,
+  0xdf, 0x71, 0x74, 0x34, 0x43, 0x55, 0xb1, 0x35, 0x09, 0x9b, 0xd9, 0xbc,
+  0x1f, 0xfa, 0xeb, 0x42, 0xd0, 0x97, 0x40, 0x72, 0xb7, 0x43, 0x96, 0x3d,
+  0xba, 0x96, 0x9d, 0x5d, 0x50, 0x02, 0x1c, 0x9b, 0x91, 0x8d, 0x9c, 0xc0,
+  0xac, 0xd7, 0xbb, 0x2f, 0x17, 0xd7, 0xcb, 0x3e, 0x82, 0x9d, 0x73, 0xeb,
+  0x07, 0x42, 0x92, 0xb2, 0xcd, 0x64, 0xb3, 0x74, 0x55, 0x1b, 0xb4, 0x4b,
+  0x86, 0x21, 0x2c, 0xf7, 0x78, 0x87, 0x32, 0xe0, 0x16, 0xe4, 0xda, 0xbd,
+  0x4c, 0x95, 0xea, 0xa4, 0x0a, 0x7e, 0xb6, 0x0a, 0x0d, 0x2e, 0x8a, 0xcf,
+  0x55, 0xab, 0xc3, 0xe5, 0xdd, 0x41, 0x8a, 0x4e, 0xe6, 0x6f, 0x65, 0x6c,
+  0xb2, 0x40, 0xcf, 0x17, 0x5d, 0xb9, 0xc3, 0x6a, 0x0b, 0x27, 0x11, 0x84,
+  0x77, 0x61, 0xf6, 0xc2, 0x7c, 0xed, 0xc0, 0x8d, 0x78, 0x14, 0x18, 0x99,
+  0x81, 0x99, 0x75, 0x63, 0xb7, 0xe8, 0x53, 0xd3, 0xba, 0x61, 0xe9, 0x0e,
+  0xfa, 0xa2, 0x30, 0xf3, 0x46, 0xa2, 0xb9, 0xc9, 0x1f, 0x6c, 0x80, 0x5a,
+  0x40, 0xac, 0x27, 0xed, 0x48, 0x47, 0x33, 0xb0, 0x54, 0xc6, 0x46, 0x1a,
+  0xf3, 0x35, 0x61, 0xc1, 0x02, 0x29, 0x90, 0x54, 0x7e, 0x64, 0x4d, 0xc4,
+  0x30, 0x52, 0x02, 0x82, 0xd7, 0xdf, 0xce, 0x21, 0x6e, 0x18, 0x91, 0xd7,
+  0xb8, 0xab, 0x8c, 0x27, 0x17, 0xb5, 0xf0, 0xa3, 0x01, 0x2f, 0x8e, 0xd2,
+  0x2e, 0x87, 0x3a, 0x3d, 0xb4, 0x29, 0x67, 0x8a, 0xc4, 0x03, 0x02, 0x03,
+  0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xad, 0x30, 0x82, 0x01, 0xa9, 0x30,
+  0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+  0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+  0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+  0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x11, 0xdb, 0x23, 0x45, 0xfd,
+  0x54, 0xcc, 0x6a, 0x71, 0x6f, 0x84, 0x8a, 0x03, 0xd7, 0xbe, 0xf7, 0x01,
+  0x2f, 0x26, 0x86, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+  0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, 0xa4, 0x40, 0x5b, 0xa5,
+  0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, 0xd0, 0x41, 0xae, 0xf2,
+  0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+  0x04, 0x5a, 0x30, 0x58, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73,
+  0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x30, 0x2d, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x74,
+  0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73,
+  0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x5b, 0x06, 0x03,
+  0x55, 0x1d, 0x1f, 0x04, 0x54, 0x30, 0x52, 0x30, 0x27, 0xa0, 0x25, 0xa0,
+  0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+  0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+  0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c,
+  0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+  0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74,
+  0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63,
+  0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+  0x20, 0x04, 0x79, 0x30, 0x77, 0x30, 0x75, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+  0x04, 0x01, 0x81, 0xb5, 0x37, 0x01, 0x02, 0x01, 0x30, 0x66, 0x30, 0x2e,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x22,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x64, 0x66, 0x30, 0x34,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x28,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+  0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65,
+  0x2e, 0x70, 0x64, 0x66, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00,
+  0x9d, 0x07, 0xe1, 0xee, 0x90, 0x76, 0x31, 0x67, 0x16, 0x45, 0x70, 0x8c,
+  0xcb, 0x84, 0x8b, 0x4b, 0x57, 0x68, 0x44, 0xa5, 0x89, 0xc1, 0xf2, 0x7e,
+  0xcb, 0x28, 0x8b, 0xf5, 0xe7, 0x70, 0x77, 0xd5, 0xb6, 0xf4, 0x0b, 0x21,
+  0x60, 0xa5, 0xa1, 0x74, 0x73, 0x24, 0x22, 0x80, 0xd6, 0xd8, 0xba, 0x8d,
+  0xa2, 0x62, 0x5d, 0x09, 0x35, 0x42, 0x29, 0xfb, 0x39, 0x63, 0x45, 0x0b,
+  0xa4, 0xb0, 0x38, 0x1a, 0x68, 0xf4, 0x95, 0x13, 0xcc, 0xe0, 0x43, 0x94,
+  0xec, 0xeb, 0x39, 0x1a, 0xec, 0x57, 0x29, 0xd9, 0x99, 0x6d, 0xf5, 0x84,
+  0xcd, 0x8e, 0x73, 0xae, 0xc9, 0xdc, 0x6a, 0xfa, 0x9e, 0x9d, 0x16, 0x64,
+  0x93, 0x08, 0xc7, 0x1c, 0xc2, 0x89, 0x54, 0x9e, 0x77, 0x80, 0x90, 0xf6,
+  0xb9, 0x29, 0x76, 0xeb, 0x13, 0x67, 0x48, 0x59, 0xf8, 0x2e, 0x3a, 0x31,
+  0xb8, 0xc9, 0xd3, 0x88, 0xe5, 0x5f, 0x4e, 0xd2, 0x19, 0x3d, 0x43, 0x8e,
+  0xd7, 0x92, 0xff, 0xcf, 0x38, 0xb6, 0xe1, 0x5b, 0x8a, 0x53, 0x1d, 0xce,
+  0xac, 0xb4, 0x76, 0x2f, 0xd8, 0xf7, 0x40, 0x63, 0xd5, 0xee, 0x69, 0xf3,
+  0x45, 0x7d, 0xa0, 0x62, 0xc1, 0x61, 0xc3, 0x75, 0xed, 0xb2, 0x7b, 0x4d,
+  0xac, 0x21, 0x27, 0x30, 0x4e, 0x59, 0x46, 0x6a, 0x93, 0x17, 0xca, 0xc8,
+  0x39, 0x2d, 0x01, 0x73, 0x65, 0x5b, 0xe9, 0x41, 0x9b, 0x11, 0x17, 0x9c,
+  0xc8, 0xc8, 0x4a, 0xef, 0xa1, 0x76, 0x60, 0x2d, 0xae, 0x93, 0xff, 0x0c,
+  0xd5, 0x33, 0x13, 0x9f, 0x4f, 0x13, 0xce, 0xdd, 0x86, 0xf1, 0xfc, 0xf8,
+  0x35, 0x54, 0x15, 0xa8, 0x5b, 0xe7, 0x85, 0x7e, 0xfa, 0x37, 0x09, 0xff,
+  0x8b, 0xb8, 0x31, 0x49, 0x9e, 0x0d, 0x6e, 0xde, 0xb4, 0xd2, 0x12, 0x2d,
+  0xb8, 0xed, 0xc8, 0xc3, 0xf1, 0xb6, 0x42, 0xa0, 0x4c, 0x97, 0x79, 0xdf,
+  0xfe, 0xc3, 0xa3, 0x9f, 0xa1, 0xf4, 0x6d, 0x2c, 0x84, 0x77, 0xa4, 0xa2,
+  0x05, 0xe1, 0x17, 0xff, 0x31, 0xdd, 0x9a, 0xf3, 0xb8, 0x7a, 0xc3, 0x52,
+  0xc2, 0x11, 0x11, 0xb7, 0x50, 0x31, 0x8a, 0x7f, 0xcc, 0xe7, 0x5a, 0x89,
+  0xcc, 0xf7, 0x86, 0x9a, 0x61, 0x92, 0x4f, 0x2f, 0x94, 0xb6, 0x98, 0xc7,
+  0x78, 0xe0, 0x62, 0x4b, 0x43, 0x7d, 0x3c, 0xde, 0xd6, 0x9a, 0xb4, 0x10,
+  0xa1, 0x40, 0x9c, 0x4b, 0x2a, 0xdc, 0xb8, 0xd0, 0xd4, 0x9e, 0xfd, 0xf1,
+  0x84, 0x78, 0x1b, 0x0e, 0x57, 0x8f, 0x69, 0x54, 0x42, 0x68, 0x7b, 0xea,
+  0xa0, 0xef, 0x75, 0x0f, 0x07, 0xa2, 0x8c, 0x73, 0x99, 0xab, 0x55, 0xf5,
+  0x07, 0x09, 0xd2, 0xaf, 0x38, 0x03, 0x6a, 0x90, 0x03, 0x0c, 0x2f, 0x8f,
+  0xe2, 0xe8, 0x43, 0xc2, 0x31, 0xe9, 0x6f, 0xad, 0x87, 0xe5, 0x8d, 0xbd,
+  0x4e, 0x2c, 0x89, 0x4b, 0x51, 0xe6, 0x9c, 0x4c, 0x54, 0x76, 0xc0, 0x12,
+  0x81, 0x53, 0x9b, 0xec, 0xa0, 0xfc, 0x2c, 0x9c, 0xda, 0x18, 0x95, 0x6e,
+  0x1e, 0x38, 0x26, 0x42, 0x27, 0x78, 0x60, 0x08, 0xdf, 0x7f, 0x6d, 0x32,
+  0xe8, 0xd8, 0xc0, 0x6f, 0x1f, 0xeb, 0x26, 0x75, 0x9f, 0x93, 0xfc, 0x7b,
+  0x1b, 0xfe, 0x35, 0x90, 0xdc, 0x53, 0xa3, 0x07, 0xa6, 0x3f, 0x83, 0x55,
+  0x0a, 0x2b, 0x4e, 0x62, 0x82, 0x25, 0xce, 0x66, 0x30, 0x5d, 0x2c, 0xe0,
+  0xf9, 0x19, 0x1b, 0x75, 0xb9, 0x9d, 0x98, 0x56, 0xa6, 0x83, 0x27, 0x7a,
+  0xd1, 0x8f, 0x8d, 0x59, 0x93, 0xfc, 0x3f, 0x73, 0xd7, 0x2e, 0xb4, 0x2c,
+  0x95, 0xd8, 0x8b, 0xf7, 0xc9, 0x7e, 0xc7, 0xfc, 0x9d, 0xac, 0x72, 0x04,
+  0x1f, 0xd2, 0xcc, 0x17, 0xf4, 0xed, 0x34, 0x60, 0x9b, 0x9e, 0x4a, 0x97,
+  0x04, 0xfe, 0xdd, 0x72, 0x0e, 0x57, 0x54, 0x51, 0x06, 0x70, 0x4d, 0xef,
+  0xaa, 0x1c, 0xa4, 0x82, 0xe0, 0x33, 0xc7, 0xf4,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:51:f9:59:81:41:45:ca:bd:e0:24:e2:12:c9:c2:0e
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Validity
+            Not Before: Apr  3 00:00:00 2007 GMT
+            Not After : Apr  3 00:00:00 2022 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bf:61:0a:29:10:1f:5e:fe:34:37:51:08:f8:1e:
+                    fb:22:ed:61:be:0b:0d:70:4c:50:63:26:75:15:b9:
+                    41:88:97:b6:f0:a0:15:bb:08:60:e0:42:e8:05:29:
+                    10:87:36:8a:28:65:a8:ef:31:07:74:6d:36:97:2f:
+                    28:46:66:04:c7:2a:79:26:7a:99:d5:8e:c3:6d:4f:
+                    a0:5e:ad:bc:3d:91:c2:59:7b:5e:36:6c:c0:53:cf:
+                    00:08:32:3e:10:64:58:10:13:69:c7:0c:ee:9c:42:
+                    51:00:f9:05:44:ee:24:ce:7a:1f:ed:8c:11:bd:12:
+                    a8:f3:15:f4:1c:7a:31:69:01:1b:a7:e6:5d:c0:9a:
+                    6c:7e:09:9e:e7:52:44:4a:10:3a:23:e4:9b:b6:03:
+                    af:a8:9c:b4:5b:9f:d4:4b:ad:92:8c:ce:b5:11:2a:
+                    aa:37:18:8d:b4:c2:b8:d8:5c:06:8c:f8:ff:23:bd:
+                    35:5e:d4:7c:3e:7e:83:0e:91:96:05:98:c3:b2:1f:
+                    e3:c8:65:eb:a9:7b:5d:a0:2c:cc:fc:3c:d9:6d:ed:
+                    cc:fa:4b:43:8c:c9:d4:b8:a5:61:1c:b2:40:b6:28:
+                    12:df:b9:f8:5f:fe:d3:b2:c9:ef:3d:b4:1e:4b:7c:
+                    1c:4c:99:36:9e:3d:eb:ec:a7:68:5e:1d:df:67:6e:
+                    5e:fb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.1.3.0.2
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+                Full Name:
+                  URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+            X509v3 Authority Key Identifier: 
+                keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+            X509v3 Subject Key Identifier: 
+                50:EA:73:89:DB:29:FB:10:8F:9E:E5:01:20:D4:DE:79:99:48:83:F7
+    Signature Algorithm: sha1WithRSAEncryption
+         5d:4f:84:f1:a8:88:d3:a3:b2:bc:9c:6d:e5:29:49:77:e1:e7:
+         d6:dc:a9:d8:35:ae:c9:71:dc:e5:db:dc:9d:24:21:90:a6:cf:
+         b7:01:1c:9b:d4:57:97:91:d7:75:16:a5:12:d7:b9:3d:2e:89:
+         3d:39:69:8a:d6:35:37:f9:f1:21:c4:5b:40:ad:59:a9:2f:5f:
+         3a:00:29:43:27:71:03:e4:bd:30:32:55:a6:fe:84:0e:0b:9b:
+         38:19:2c:43:7c:ac:43:bf:75:31:e5:23:1c:45:55:b7:69:08:
+         91:b5:cf:d7:d5:b1:5e:ee:9f:94:e4:d6:7a:b9:18:c3:b8:d6:
+         52:63:1c:10:ba:8b:2f:6d:5d:cc:05:38:f4:56:05:6d:ef:9e:
+         ec:e8:61:36:0c:14:4b:85:14:5a:0c:83:4f:22:5c:59:cb:8c:
+         8a:71:da:fa:c5:10:84:58:cf:07:ee:e3:90:c2:f5:f9:29:c7:
+         5a:23:71:f9:59:b4:64:2b:88:b0:a7:36:c7:9a:20:61:eb:fa:
+         4e:b5:ae:6b:1b:e4:e3:ec:e2:d9:3c:41:49:a8:20:a4:54:f5:
+         92:8d:bb:c0:55:20:04:a6:d8:b0:17:16:cc:e3:d0:c8:b4:3d:
+         e5:d9:84:c6:d3:f6:6e:6d:78:c9:79:43:e8:7a:37:ff:5c:35:
+         49:bf:a1:c5
+-----BEGIN CERTIFICATE-----
+MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
+CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
+KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
+BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
+1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
+zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
+32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
+ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
+AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
+AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
+AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
+AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
+AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
+AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
+AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG
+CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB
+hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz
+c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
+Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe
+eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1
+rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv
+XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk
+1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF
+EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU
+9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert98[] = {
+  0x30, 0x82, 0x06, 0x55, 0x30, 0x82, 0x05, 0x3d, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x08, 0x51, 0xf9, 0x59, 0x81, 0x41, 0x45, 0xca, 0xbd,
+  0xe0, 0x24, 0xe2, 0x12, 0xc9, 0xc2, 0x0e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+  0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+  0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+  0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x30, 0x33, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x30,
+  0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+  0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+  0x43, 0x41, 0x2d, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xbf, 0x61, 0x0a, 0x29, 0x10, 0x1f, 0x5e, 0xfe, 0x34, 0x37, 0x51,
+  0x08, 0xf8, 0x1e, 0xfb, 0x22, 0xed, 0x61, 0xbe, 0x0b, 0x0d, 0x70, 0x4c,
+  0x50, 0x63, 0x26, 0x75, 0x15, 0xb9, 0x41, 0x88, 0x97, 0xb6, 0xf0, 0xa0,
+  0x15, 0xbb, 0x08, 0x60, 0xe0, 0x42, 0xe8, 0x05, 0x29, 0x10, 0x87, 0x36,
+  0x8a, 0x28, 0x65, 0xa8, 0xef, 0x31, 0x07, 0x74, 0x6d, 0x36, 0x97, 0x2f,
+  0x28, 0x46, 0x66, 0x04, 0xc7, 0x2a, 0x79, 0x26, 0x7a, 0x99, 0xd5, 0x8e,
+  0xc3, 0x6d, 0x4f, 0xa0, 0x5e, 0xad, 0xbc, 0x3d, 0x91, 0xc2, 0x59, 0x7b,
+  0x5e, 0x36, 0x6c, 0xc0, 0x53, 0xcf, 0x00, 0x08, 0x32, 0x3e, 0x10, 0x64,
+  0x58, 0x10, 0x13, 0x69, 0xc7, 0x0c, 0xee, 0x9c, 0x42, 0x51, 0x00, 0xf9,
+  0x05, 0x44, 0xee, 0x24, 0xce, 0x7a, 0x1f, 0xed, 0x8c, 0x11, 0xbd, 0x12,
+  0xa8, 0xf3, 0x15, 0xf4, 0x1c, 0x7a, 0x31, 0x69, 0x01, 0x1b, 0xa7, 0xe6,
+  0x5d, 0xc0, 0x9a, 0x6c, 0x7e, 0x09, 0x9e, 0xe7, 0x52, 0x44, 0x4a, 0x10,
+  0x3a, 0x23, 0xe4, 0x9b, 0xb6, 0x03, 0xaf, 0xa8, 0x9c, 0xb4, 0x5b, 0x9f,
+  0xd4, 0x4b, 0xad, 0x92, 0x8c, 0xce, 0xb5, 0x11, 0x2a, 0xaa, 0x37, 0x18,
+  0x8d, 0xb4, 0xc2, 0xb8, 0xd8, 0x5c, 0x06, 0x8c, 0xf8, 0xff, 0x23, 0xbd,
+  0x35, 0x5e, 0xd4, 0x7c, 0x3e, 0x7e, 0x83, 0x0e, 0x91, 0x96, 0x05, 0x98,
+  0xc3, 0xb2, 0x1f, 0xe3, 0xc8, 0x65, 0xeb, 0xa9, 0x7b, 0x5d, 0xa0, 0x2c,
+  0xcc, 0xfc, 0x3c, 0xd9, 0x6d, 0xed, 0xcc, 0xfa, 0x4b, 0x43, 0x8c, 0xc9,
+  0xd4, 0xb8, 0xa5, 0x61, 0x1c, 0xb2, 0x40, 0xb6, 0x28, 0x12, 0xdf, 0xb9,
+  0xf8, 0x5f, 0xfe, 0xd3, 0xb2, 0xc9, 0xef, 0x3d, 0xb4, 0x1e, 0x4b, 0x7c,
+  0x1c, 0x4c, 0x99, 0x36, 0x9e, 0x3d, 0xeb, 0xec, 0xa7, 0x68, 0x5e, 0x1d,
+  0xdf, 0x67, 0x6e, 0x5e, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x02, 0xf7, 0x30, 0x82, 0x02, 0xf3, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x82,
+  0x01, 0xc6, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x82, 0x01, 0xbd, 0x30,
+  0x82, 0x01, 0xb9, 0x30, 0x82, 0x01, 0xb5, 0x06, 0x0b, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xfd, 0x6c, 0x01, 0x03, 0x00, 0x02, 0x30, 0x82, 0x01, 0xa4,
+  0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+  0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+  0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, 0x00, 0x41,
+  0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65,
+  0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68,
+  0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72,
+  0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e,
+  0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, 0x00, 0x74,
+  0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63,
+  0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63,
+  0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67,
+  0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x20,
+  0x00, 0x43, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x50, 0x00, 0x53,
+  0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, 0x00, 0x6c,
+  0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x50,
+  0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41,
+  0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65,
+  0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, 0x00, 0x69,
+  0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6d,
+  0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x61,
+  0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79,
+  0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x61,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x63,
+  0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x62,
+  0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x2e,
+  0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+  0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06,
+  0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68,
+  0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64,
+  0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+  0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30, 0x81,
+  0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74,
+  0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67,
+  0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69,
+  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73,
+  0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f,
+  0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e, 0xa0,
+  0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+  0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+  0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+  0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72,
+  0x6c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+  0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4,
+  0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x1d,
+  0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x50, 0xea, 0x73,
+  0x89, 0xdb, 0x29, 0xfb, 0x10, 0x8f, 0x9e, 0xe5, 0x01, 0x20, 0xd4, 0xde,
+  0x79, 0x99, 0x48, 0x83, 0xf7, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+  0x00, 0x5d, 0x4f, 0x84, 0xf1, 0xa8, 0x88, 0xd3, 0xa3, 0xb2, 0xbc, 0x9c,
+  0x6d, 0xe5, 0x29, 0x49, 0x77, 0xe1, 0xe7, 0xd6, 0xdc, 0xa9, 0xd8, 0x35,
+  0xae, 0xc9, 0x71, 0xdc, 0xe5, 0xdb, 0xdc, 0x9d, 0x24, 0x21, 0x90, 0xa6,
+  0xcf, 0xb7, 0x01, 0x1c, 0x9b, 0xd4, 0x57, 0x97, 0x91, 0xd7, 0x75, 0x16,
+  0xa5, 0x12, 0xd7, 0xb9, 0x3d, 0x2e, 0x89, 0x3d, 0x39, 0x69, 0x8a, 0xd6,
+  0x35, 0x37, 0xf9, 0xf1, 0x21, 0xc4, 0x5b, 0x40, 0xad, 0x59, 0xa9, 0x2f,
+  0x5f, 0x3a, 0x00, 0x29, 0x43, 0x27, 0x71, 0x03, 0xe4, 0xbd, 0x30, 0x32,
+  0x55, 0xa6, 0xfe, 0x84, 0x0e, 0x0b, 0x9b, 0x38, 0x19, 0x2c, 0x43, 0x7c,
+  0xac, 0x43, 0xbf, 0x75, 0x31, 0xe5, 0x23, 0x1c, 0x45, 0x55, 0xb7, 0x69,
+  0x08, 0x91, 0xb5, 0xcf, 0xd7, 0xd5, 0xb1, 0x5e, 0xee, 0x9f, 0x94, 0xe4,
+  0xd6, 0x7a, 0xb9, 0x18, 0xc3, 0xb8, 0xd6, 0x52, 0x63, 0x1c, 0x10, 0xba,
+  0x8b, 0x2f, 0x6d, 0x5d, 0xcc, 0x05, 0x38, 0xf4, 0x56, 0x05, 0x6d, 0xef,
+  0x9e, 0xec, 0xe8, 0x61, 0x36, 0x0c, 0x14, 0x4b, 0x85, 0x14, 0x5a, 0x0c,
+  0x83, 0x4f, 0x22, 0x5c, 0x59, 0xcb, 0x8c, 0x8a, 0x71, 0xda, 0xfa, 0xc5,
+  0x10, 0x84, 0x58, 0xcf, 0x07, 0xee, 0xe3, 0x90, 0xc2, 0xf5, 0xf9, 0x29,
+  0xc7, 0x5a, 0x23, 0x71, 0xf9, 0x59, 0xb4, 0x64, 0x2b, 0x88, 0xb0, 0xa7,
+  0x36, 0xc7, 0x9a, 0x20, 0x61, 0xeb, 0xfa, 0x4e, 0xb5, 0xae, 0x6b, 0x1b,
+  0xe4, 0xe3, 0xec, 0xe2, 0xd9, 0x3c, 0x41, 0x49, 0xa8, 0x20, 0xa4, 0x54,
+  0xf5, 0x92, 0x8d, 0xbb, 0xc0, 0x55, 0x20, 0x04, 0xa6, 0xd8, 0xb0, 0x17,
+  0x16, 0xcc, 0xe3, 0xd0, 0xc8, 0xb4, 0x3d, 0xe5, 0xd9, 0x84, 0xc6, 0xd3,
+  0xf6, 0x6e, 0x6d, 0x78, 0xc9, 0x79, 0x43, 0xe8, 0x7a, 0x37, 0xff, 0x5c,
+  0x35, 0x49, 0xbf, 0xa1, 0xc5,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            0a:5f:11:4d:03:5b:17:91:17:d2:ef:d4:03:8c:3f:3b
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Validity
+            Not Before: Apr  2 12:00:00 2008 GMT
+            Not After : Apr  3 00:00:00 2022 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:bf:61:0a:29:10:1f:5e:fe:34:37:51:08:f8:1e:
+                    fb:22:ed:61:be:0b:0d:70:4c:50:63:26:75:15:b9:
+                    41:88:97:b6:f0:a0:15:bb:08:60:e0:42:e8:05:29:
+                    10:87:36:8a:28:65:a8:ef:31:07:74:6d:36:97:2f:
+                    28:46:66:04:c7:2a:79:26:7a:99:d5:8e:c3:6d:4f:
+                    a0:5e:ad:bc:3d:91:c2:59:7b:5e:36:6c:c0:53:cf:
+                    00:08:32:3e:10:64:58:10:13:69:c7:0c:ee:9c:42:
+                    51:00:f9:05:44:ee:24:ce:7a:1f:ed:8c:11:bd:12:
+                    a8:f3:15:f4:1c:7a:31:69:01:1b:a7:e6:5d:c0:9a:
+                    6c:7e:09:9e:e7:52:44:4a:10:3a:23:e4:9b:b6:03:
+                    af:a8:9c:b4:5b:9f:d4:4b:ad:92:8c:ce:b5:11:2a:
+                    aa:37:18:8d:b4:c2:b8:d8:5c:06:8c:f8:ff:23:bd:
+                    35:5e:d4:7c:3e:7e:83:0e:91:96:05:98:c3:b2:1f:
+                    e3:c8:65:eb:a9:7b:5d:a0:2c:cc:fc:3c:d9:6d:ed:
+                    cc:fa:4b:43:8c:c9:d4:b8:a5:61:1c:b2:40:b6:28:
+                    12:df:b9:f8:5f:fe:d3:b2:c9:ef:3d:b4:1e:4b:7c:
+                    1c:4c:99:36:9e:3d:eb:ec:a7:68:5e:1d:df:67:6e:
+                    5e:fb
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.1.3.0.2
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+                Full Name:
+                  URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+            X509v3 Authority Key Identifier: 
+                keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+            X509v3 Subject Key Identifier: 
+                50:EA:73:89:DB:29:FB:10:8F:9E:E5:01:20:D4:DE:79:99:48:83:F7
+    Signature Algorithm: sha1WithRSAEncryption
+         1e:e2:a5:48:9e:6c:db:53:38:0f:ef:a6:1a:2a:ac:e2:03:43:
+         ed:9a:bc:3e:8e:75:1b:f0:fd:2e:22:59:ac:13:c0:61:e2:e7:
+         fa:e9:99:cd:87:09:75:54:28:bf:46:60:dc:be:51:2c:92:f3:
+         1b:91:7c:31:08:70:e2:37:b9:c1:5b:a8:bd:a3:0b:00:fb:1a:
+         15:fd:03:ad:58:6a:c5:c7:24:99:48:47:46:31:1e:92:ef:b4:
+         5f:4e:34:c7:90:bf:31:c1:f8:b1:84:86:d0:9c:01:aa:df:8a:
+         56:06:ce:3a:e9:0e:ae:97:74:5d:d7:71:9a:42:74:5f:de:8d:
+         43:7c:de:e9:55:ed:69:00:cb:05:e0:7a:61:61:33:d1:19:4d:
+         f9:08:ee:a0:39:c5:25:35:b7:2b:c4:0f:b2:dd:f1:a5:b7:0e:
+         24:c4:26:28:8d:79:77:f5:2f:f0:57:ba:7c:07:d4:e1:fc:cd:
+         5a:30:57:7e:86:10:47:dd:31:1f:d7:fc:a2:c2:bf:30:7c:5d:
+         24:aa:e8:f9:ae:5f:6a:74:c2:ce:6b:b3:46:d8:21:be:29:d4:
+         8e:5e:15:d6:42:4a:e7:32:6f:a4:b1:6b:51:83:58:be:3f:6d:
+         c7:fb:da:03:21:cb:6a:16:19:4e:0a:f0:ad:84:ca:5d:94:b3:
+         5a:76:f7:61
+-----BEGIN CERTIFICATE-----
+MIIGWDCCBUCgAwIBAgIQCl8RTQNbF5EX0u/UA4w/OzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA4MDQwMjEyMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
+CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
+KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
+BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
+1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
+zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
+32duXvsCAwEAAaOCAvowggL2MA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
+ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
+AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
+AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
+AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
+AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
+AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
+AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
+AHIAZQBuAGMAZQAuMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAm
+MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSB
+hzCBhDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGln
+aEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNl
+cnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSME
+GDAWgBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUB
+INTeeZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAB7ipUiebNtTOA/vphoqrOIDQ+2a
+vD6OdRvw/S4iWawTwGHi5/rpmc2HCXVUKL9GYNy+USyS8xuRfDEIcOI3ucFbqL2j
+CwD7GhX9A61YasXHJJlIR0YxHpLvtF9ONMeQvzHB+LGEhtCcAarfilYGzjrpDq6X
+dF3XcZpCdF/ejUN83ulV7WkAywXgemFhM9EZTfkI7qA5xSU1tyvED7Ld8aW3DiTE
+JiiNeXf1L/BXunwH1OH8zVowV36GEEfdMR/X/KLCvzB8XSSq6PmuX2p0ws5rs0bY
+Ib4p1I5eFdZCSucyb6Sxa1GDWL4/bcf72gMhy2oWGU4K8K2Eyl2Us1p292E=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert99[] = {
+  0x30, 0x82, 0x06, 0x58, 0x30, 0x82, 0x05, 0x40, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x0a, 0x5f, 0x11, 0x4d, 0x03, 0x5b, 0x17, 0x91, 0x17,
+  0xd2, 0xef, 0xd4, 0x03, 0x8c, 0x3f, 0x3b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+  0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+  0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+  0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x34, 0x30, 0x32, 0x31, 0x32,
+  0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x30,
+  0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+  0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+  0x43, 0x41, 0x2d, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+  0x00, 0xbf, 0x61, 0x0a, 0x29, 0x10, 0x1f, 0x5e, 0xfe, 0x34, 0x37, 0x51,
+  0x08, 0xf8, 0x1e, 0xfb, 0x22, 0xed, 0x61, 0xbe, 0x0b, 0x0d, 0x70, 0x4c,
+  0x50, 0x63, 0x26, 0x75, 0x15, 0xb9, 0x41, 0x88, 0x97, 0xb6, 0xf0, 0xa0,
+  0x15, 0xbb, 0x08, 0x60, 0xe0, 0x42, 0xe8, 0x05, 0x29, 0x10, 0x87, 0x36,
+  0x8a, 0x28, 0x65, 0xa8, 0xef, 0x31, 0x07, 0x74, 0x6d, 0x36, 0x97, 0x2f,
+  0x28, 0x46, 0x66, 0x04, 0xc7, 0x2a, 0x79, 0x26, 0x7a, 0x99, 0xd5, 0x8e,
+  0xc3, 0x6d, 0x4f, 0xa0, 0x5e, 0xad, 0xbc, 0x3d, 0x91, 0xc2, 0x59, 0x7b,
+  0x5e, 0x36, 0x6c, 0xc0, 0x53, 0xcf, 0x00, 0x08, 0x32, 0x3e, 0x10, 0x64,
+  0x58, 0x10, 0x13, 0x69, 0xc7, 0x0c, 0xee, 0x9c, 0x42, 0x51, 0x00, 0xf9,
+  0x05, 0x44, 0xee, 0x24, 0xce, 0x7a, 0x1f, 0xed, 0x8c, 0x11, 0xbd, 0x12,
+  0xa8, 0xf3, 0x15, 0xf4, 0x1c, 0x7a, 0x31, 0x69, 0x01, 0x1b, 0xa7, 0xe6,
+  0x5d, 0xc0, 0x9a, 0x6c, 0x7e, 0x09, 0x9e, 0xe7, 0x52, 0x44, 0x4a, 0x10,
+  0x3a, 0x23, 0xe4, 0x9b, 0xb6, 0x03, 0xaf, 0xa8, 0x9c, 0xb4, 0x5b, 0x9f,
+  0xd4, 0x4b, 0xad, 0x92, 0x8c, 0xce, 0xb5, 0x11, 0x2a, 0xaa, 0x37, 0x18,
+  0x8d, 0xb4, 0xc2, 0xb8, 0xd8, 0x5c, 0x06, 0x8c, 0xf8, 0xff, 0x23, 0xbd,
+  0x35, 0x5e, 0xd4, 0x7c, 0x3e, 0x7e, 0x83, 0x0e, 0x91, 0x96, 0x05, 0x98,
+  0xc3, 0xb2, 0x1f, 0xe3, 0xc8, 0x65, 0xeb, 0xa9, 0x7b, 0x5d, 0xa0, 0x2c,
+  0xcc, 0xfc, 0x3c, 0xd9, 0x6d, 0xed, 0xcc, 0xfa, 0x4b, 0x43, 0x8c, 0xc9,
+  0xd4, 0xb8, 0xa5, 0x61, 0x1c, 0xb2, 0x40, 0xb6, 0x28, 0x12, 0xdf, 0xb9,
+  0xf8, 0x5f, 0xfe, 0xd3, 0xb2, 0xc9, 0xef, 0x3d, 0xb4, 0x1e, 0x4b, 0x7c,
+  0x1c, 0x4c, 0x99, 0x36, 0x9e, 0x3d, 0xeb, 0xec, 0xa7, 0x68, 0x5e, 0x1d,
+  0xdf, 0x67, 0x6e, 0x5e, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+  0x02, 0xfa, 0x30, 0x82, 0x02, 0xf6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+  0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x82,
+  0x01, 0xc6, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x82, 0x01, 0xbd, 0x30,
+  0x82, 0x01, 0xb9, 0x30, 0x82, 0x01, 0xb5, 0x06, 0x0b, 0x60, 0x86, 0x48,
+  0x01, 0x86, 0xfd, 0x6c, 0x01, 0x03, 0x00, 0x02, 0x30, 0x82, 0x01, 0xa4,
+  0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+  0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+  0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+  0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, 0x00, 0x41,
+  0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65,
+  0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68,
+  0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72,
+  0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e,
+  0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, 0x00, 0x74,
+  0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63,
+  0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63,
+  0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67,
+  0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x20,
+  0x00, 0x43, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x50, 0x00, 0x53,
+  0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, 0x00, 0x6c,
+  0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x50,
+  0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41,
+  0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65,
+  0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, 0x00, 0x69,
+  0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6d,
+  0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x61,
+  0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79,
+  0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x61,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x63,
+  0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x62,
+  0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x2e,
+  0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+  0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x34, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26,
+  0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81,
+  0x87, 0x30, 0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e,
+  0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+  0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67,
+  0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56,
+  0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40,
+  0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+  0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65,
+  0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43,
+  0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72,
+  0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41,
+  0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+  0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf,
+  0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b,
+  0xc3, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+  0x50, 0xea, 0x73, 0x89, 0xdb, 0x29, 0xfb, 0x10, 0x8f, 0x9e, 0xe5, 0x01,
+  0x20, 0xd4, 0xde, 0x79, 0x99, 0x48, 0x83, 0xf7, 0x30, 0x0d, 0x06, 0x09,
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+  0x82, 0x01, 0x01, 0x00, 0x1e, 0xe2, 0xa5, 0x48, 0x9e, 0x6c, 0xdb, 0x53,
+  0x38, 0x0f, 0xef, 0xa6, 0x1a, 0x2a, 0xac, 0xe2, 0x03, 0x43, 0xed, 0x9a,
+  0xbc, 0x3e, 0x8e, 0x75, 0x1b, 0xf0, 0xfd, 0x2e, 0x22, 0x59, 0xac, 0x13,
+  0xc0, 0x61, 0xe2, 0xe7, 0xfa, 0xe9, 0x99, 0xcd, 0x87, 0x09, 0x75, 0x54,
+  0x28, 0xbf, 0x46, 0x60, 0xdc, 0xbe, 0x51, 0x2c, 0x92, 0xf3, 0x1b, 0x91,
+  0x7c, 0x31, 0x08, 0x70, 0xe2, 0x37, 0xb9, 0xc1, 0x5b, 0xa8, 0xbd, 0xa3,
+  0x0b, 0x00, 0xfb, 0x1a, 0x15, 0xfd, 0x03, 0xad, 0x58, 0x6a, 0xc5, 0xc7,
+  0x24, 0x99, 0x48, 0x47, 0x46, 0x31, 0x1e, 0x92, 0xef, 0xb4, 0x5f, 0x4e,
+  0x34, 0xc7, 0x90, 0xbf, 0x31, 0xc1, 0xf8, 0xb1, 0x84, 0x86, 0xd0, 0x9c,
+  0x01, 0xaa, 0xdf, 0x8a, 0x56, 0x06, 0xce, 0x3a, 0xe9, 0x0e, 0xae, 0x97,
+  0x74, 0x5d, 0xd7, 0x71, 0x9a, 0x42, 0x74, 0x5f, 0xde, 0x8d, 0x43, 0x7c,
+  0xde, 0xe9, 0x55, 0xed, 0x69, 0x00, 0xcb, 0x05, 0xe0, 0x7a, 0x61, 0x61,
+  0x33, 0xd1, 0x19, 0x4d, 0xf9, 0x08, 0xee, 0xa0, 0x39, 0xc5, 0x25, 0x35,
+  0xb7, 0x2b, 0xc4, 0x0f, 0xb2, 0xdd, 0xf1, 0xa5, 0xb7, 0x0e, 0x24, 0xc4,
+  0x26, 0x28, 0x8d, 0x79, 0x77, 0xf5, 0x2f, 0xf0, 0x57, 0xba, 0x7c, 0x07,
+  0xd4, 0xe1, 0xfc, 0xcd, 0x5a, 0x30, 0x57, 0x7e, 0x86, 0x10, 0x47, 0xdd,
+  0x31, 0x1f, 0xd7, 0xfc, 0xa2, 0xc2, 0xbf, 0x30, 0x7c, 0x5d, 0x24, 0xaa,
+  0xe8, 0xf9, 0xae, 0x5f, 0x6a, 0x74, 0xc2, 0xce, 0x6b, 0xb3, 0x46, 0xd8,
+  0x21, 0xbe, 0x29, 0xd4, 0x8e, 0x5e, 0x15, 0xd6, 0x42, 0x4a, 0xe7, 0x32,
+  0x6f, 0xa4, 0xb1, 0x6b, 0x51, 0x83, 0x58, 0xbe, 0x3f, 0x6d, 0xc7, 0xfb,
+  0xda, 0x03, 0x21, 0xcb, 0x6a, 0x16, 0x19, 0x4e, 0x0a, 0xf0, 0xad, 0x84,
+  0xca, 0x5d, 0x94, 0xb3, 0x5a, 0x76, 0xf7, 0x61,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:bb:b0:25:47:13:4b:c9:b1:10:d7:c1:a2:12:59:c5
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Validity
+            Not Before: Nov 10 00:00:00 2006 GMT
+            Not After : Nov 10 00:00:00 2021 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:f3:96:62:d8:75:6e:19:ff:3f:34:7c:49:4f:31:
+                    7e:0d:04:4e:99:81:e2:b3:85:55:91:30:b1:c0:af:
+                    70:bb:2c:a8:e7:18:aa:3f:78:f7:90:68:52:86:01:
+                    88:97:e2:3b:06:65:90:aa:bd:65:76:c2:ec:be:10:
+                    5b:37:78:83:60:75:45:c6:bd:74:aa:b6:9f:a4:3a:
+                    01:50:17:c4:39:69:b9:f1:4f:ef:82:c1:ca:f3:4a:
+                    db:cc:9e:50:4f:4d:40:a3:3a:90:e7:86:66:bc:f0:
+                    3e:76:28:4c:d1:75:80:9e:6a:35:14:35:03:9e:db:
+                    0c:8c:c2:28:ad:50:b2:ce:f6:91:a3:c3:a5:0a:58:
+                    49:f6:75:44:6c:ba:f9:ce:e9:ab:3a:02:e0:4d:f3:
+                    ac:e2:7a:e0:60:22:05:3c:82:d3:52:e2:f3:9c:47:
+                    f8:3b:d8:b2:4b:93:56:4a:bf:70:ab:3e:e9:68:c8:
+                    1d:8f:58:1d:2a:4d:5e:27:3d:ad:0a:59:2f:5a:11:
+                    20:40:d9:68:04:68:2d:f4:c0:84:0b:0a:1b:78:df:
+                    ed:1a:58:dc:fb:41:5a:6d:6b:f2:ed:1c:ee:5c:32:
+                    b6:5c:ec:d7:a6:03:32:a6:e8:de:b7:28:27:59:88:
+                    80:ff:7b:ad:89:58:d5:1e:14:a4:f2:b0:70:d4:a0:
+                    3e:a7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.2.1
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+                CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+                Full Name:
+                  URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+            X509v3 Subject Key Identifier: 
+                4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+            X509v3 Authority Key Identifier: 
+                keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+    Signature Algorithm: sha1WithRSAEncryption
+         50:1e:43:b0:f7:4d:29:96:5b:bb:a7:d3:0a:b5:b5:d5:d0:27:
+         aa:f9:af:c7:25:d1:95:d5:2f:5a:53:bd:42:07:7e:78:49:ca:
+         0b:eb:4c:55:e2:ea:2f:7f:49:ad:c7:ff:d1:2d:3e:9c:a0:64:
+         2b:51:9e:91:26:28:bb:87:bb:75:7c:bc:a1:fd:66:68:2e:4c:
+         4a:16:cc:fe:06:cf:31:ea:80:6e:e4:bd:e8:03:72:f6:25:b5:
+         41:83:61:d0:97:0a:27:1d:b3:f7:2b:32:84:8f:5b:e7:cc:3f:
+         e2:2c:67:86:94:f4:b2:2b:6c:52:3b:67:2a:8d:58:95:00:14:
+         46:24:ac:0b:fa:c9:8e:c7:26:80:df:d1:e1:97:e3:f8:bb:68:
+         c6:9c:bd:be:08:54:3b:10:32:7c:81:1f:2b:28:95:a8:41:0a:
+         c6:d0:30:66:b4:e9:f2:a2:00:69:20:07:ca:82:4c:1e:cf:a7:
+         98:b8:0c:ee:cd:16:1c:be:1a:63:d4:c0:99:f6:67:b2:f0:8e:
+         17:2d:58:c2:80:aa:5d:96:c7:b3:28:ed:f0:da:8e:b6:47:1b:
+         8f:4e:15:f1:97:4c:0b:4b:af:81:d4:46:94:62:2c:43:a7:3c:
+         25:48:19:63:f2:5c:aa:15:89:76:84:85:73:91:7d:28:3c:09:
+         83:82:bc:f7
+-----BEGIN CERTIFICATE-----
+MIIG4zCCBcugAwIBAgIQCLuwJUcTS8mxENfBohJZxTANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/
+PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC
+7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw
+PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6
+4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo
+LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U
+pPKwcNSgPqcCAwEAAaOCA4IwggN+MA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy
+BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH
+AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wDwYDVR0TAQH/BAUwAwEB/zCBgwYI
+KwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
+b20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2VydHMv
+RGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcwgYQw
+QKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1
+cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv
+bS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYEFExY
+yyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9j
+ZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBQHkOw900pllu7p9MKtbXV0Ceq+a/HJdGV
+1S9aU71CB354ScoL60xV4uovf0mtx//RLT6coGQrUZ6RJii7h7t1fLyh/WZoLkxK
+Fsz+Bs8x6oBu5L3oA3L2JbVBg2HQlwonHbP3KzKEj1vnzD/iLGeGlPSyK2xSO2cq
+jViVABRGJKwL+smOxyaA39Hhl+P4u2jGnL2+CFQ7EDJ8gR8rKJWoQQrG0DBmtOny
+ogBpIAfKgkwez6eYuAzuzRYcvhpj1MCZ9mey8I4XLVjCgKpdlsezKO3w2o62RxuP
+ThXxl0wLS6+B1EaUYixDpzwlSBlj8lyqFYl2hIVzkX0oPAmDgrz3
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert100[] = {
+  0x30, 0x82, 0x06, 0xe3, 0x30, 0x82, 0x05, 0xcb, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x08, 0xbb, 0xb0, 0x25, 0x47, 0x13, 0x4b, 0xc9, 0xb1,
+  0x10, 0xd7, 0xc1, 0xa2, 0x12, 0x59, 0xc5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+  0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+  0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+  0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30,
+  0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1f,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+  0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+  0x45, 0x56, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xf3, 0x96, 0x62, 0xd8, 0x75, 0x6e, 0x19, 0xff,
+  0x3f, 0x34, 0x7c, 0x49, 0x4f, 0x31, 0x7e, 0x0d, 0x04, 0x4e, 0x99, 0x81,
+  0xe2, 0xb3, 0x85, 0x55, 0x91, 0x30, 0xb1, 0xc0, 0xaf, 0x70, 0xbb, 0x2c,
+  0xa8, 0xe7, 0x18, 0xaa, 0x3f, 0x78, 0xf7, 0x90, 0x68, 0x52, 0x86, 0x01,
+  0x88, 0x97, 0xe2, 0x3b, 0x06, 0x65, 0x90, 0xaa, 0xbd, 0x65, 0x76, 0xc2,
+  0xec, 0xbe, 0x10, 0x5b, 0x37, 0x78, 0x83, 0x60, 0x75, 0x45, 0xc6, 0xbd,
+  0x74, 0xaa, 0xb6, 0x9f, 0xa4, 0x3a, 0x01, 0x50, 0x17, 0xc4, 0x39, 0x69,
+  0xb9, 0xf1, 0x4f, 0xef, 0x82, 0xc1, 0xca, 0xf3, 0x4a, 0xdb, 0xcc, 0x9e,
+  0x50, 0x4f, 0x4d, 0x40, 0xa3, 0x3a, 0x90, 0xe7, 0x86, 0x66, 0xbc, 0xf0,
+  0x3e, 0x76, 0x28, 0x4c, 0xd1, 0x75, 0x80, 0x9e, 0x6a, 0x35, 0x14, 0x35,
+  0x03, 0x9e, 0xdb, 0x0c, 0x8c, 0xc2, 0x28, 0xad, 0x50, 0xb2, 0xce, 0xf6,
+  0x91, 0xa3, 0xc3, 0xa5, 0x0a, 0x58, 0x49, 0xf6, 0x75, 0x44, 0x6c, 0xba,
+  0xf9, 0xce, 0xe9, 0xab, 0x3a, 0x02, 0xe0, 0x4d, 0xf3, 0xac, 0xe2, 0x7a,
+  0xe0, 0x60, 0x22, 0x05, 0x3c, 0x82, 0xd3, 0x52, 0xe2, 0xf3, 0x9c, 0x47,
+  0xf8, 0x3b, 0xd8, 0xb2, 0x4b, 0x93, 0x56, 0x4a, 0xbf, 0x70, 0xab, 0x3e,
+  0xe9, 0x68, 0xc8, 0x1d, 0x8f, 0x58, 0x1d, 0x2a, 0x4d, 0x5e, 0x27, 0x3d,
+  0xad, 0x0a, 0x59, 0x2f, 0x5a, 0x11, 0x20, 0x40, 0xd9, 0x68, 0x04, 0x68,
+  0x2d, 0xf4, 0xc0, 0x84, 0x0b, 0x0a, 0x1b, 0x78, 0xdf, 0xed, 0x1a, 0x58,
+  0xdc, 0xfb, 0x41, 0x5a, 0x6d, 0x6b, 0xf2, 0xed, 0x1c, 0xee, 0x5c, 0x32,
+  0xb6, 0x5c, 0xec, 0xd7, 0xa6, 0x03, 0x32, 0xa6, 0xe8, 0xde, 0xb7, 0x28,
+  0x27, 0x59, 0x88, 0x80, 0xff, 0x7b, 0xad, 0x89, 0x58, 0xd5, 0x1e, 0x14,
+  0xa4, 0xf2, 0xb0, 0x70, 0xd4, 0xa0, 0x3e, 0xa7, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x03, 0x82, 0x30, 0x82, 0x03, 0x7e, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x86, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x34, 0x30, 0x32,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x08, 0x30, 0x82, 0x01, 0xc4, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0xb7, 0x30, 0x82, 0x01, 0xb3, 0x06,
+  0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6c, 0x02, 0x01, 0x30, 0x82,
+  0x01, 0xa4, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d,
+  0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68,
+  0x74, 0x6d, 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52,
+  0x00, 0x41, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73,
+  0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63,
+  0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f,
+  0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63,
+  0x00, 0x63, 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e,
+  0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20,
+  0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69,
+  0x00, 0x67, 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74,
+  0x00, 0x20, 0x00, 0x45, 0x00, 0x56, 0x00, 0x20, 0x00, 0x43, 0x00, 0x50,
+  0x00, 0x53, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+  0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65,
+  0x00, 0x6c, 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20,
+  0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20,
+  0x00, 0x41, 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d,
+  0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68,
+  0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+  0x00, 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+  0x00, 0x61, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74,
+  0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+  0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e,
+  0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72,
+  0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68,
+  0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20,
+  0x00, 0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66,
+  0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65,
+  0x00, 0x2e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x81, 0x83, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x77, 0x30, 0x75,
+  0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+  0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+  0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+  0x6f, 0x6d, 0x30, 0x4d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x30, 0x02, 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x73, 0x2f,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68,
+  0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52,
+  0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x81, 0x8f,
+  0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30, 0x81, 0x84, 0x30,
+  0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63,
+  0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69,
+  0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75,
+  0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43,
+  0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86,
+  0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x34,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69,
+  0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45,
+  0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+  0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4c, 0x58,
+  0xcb, 0x25, 0xf0, 0x41, 0x4f, 0x52, 0xf4, 0x28, 0xc8, 0x81, 0x43, 0x9b,
+  0xa6, 0xa8, 0xa0, 0xe6, 0x92, 0xe5, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+  0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03,
+  0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63,
+  0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x50,
+  0x1e, 0x43, 0xb0, 0xf7, 0x4d, 0x29, 0x96, 0x5b, 0xbb, 0xa7, 0xd3, 0x0a,
+  0xb5, 0xb5, 0xd5, 0xd0, 0x27, 0xaa, 0xf9, 0xaf, 0xc7, 0x25, 0xd1, 0x95,
+  0xd5, 0x2f, 0x5a, 0x53, 0xbd, 0x42, 0x07, 0x7e, 0x78, 0x49, 0xca, 0x0b,
+  0xeb, 0x4c, 0x55, 0xe2, 0xea, 0x2f, 0x7f, 0x49, 0xad, 0xc7, 0xff, 0xd1,
+  0x2d, 0x3e, 0x9c, 0xa0, 0x64, 0x2b, 0x51, 0x9e, 0x91, 0x26, 0x28, 0xbb,
+  0x87, 0xbb, 0x75, 0x7c, 0xbc, 0xa1, 0xfd, 0x66, 0x68, 0x2e, 0x4c, 0x4a,
+  0x16, 0xcc, 0xfe, 0x06, 0xcf, 0x31, 0xea, 0x80, 0x6e, 0xe4, 0xbd, 0xe8,
+  0x03, 0x72, 0xf6, 0x25, 0xb5, 0x41, 0x83, 0x61, 0xd0, 0x97, 0x0a, 0x27,
+  0x1d, 0xb3, 0xf7, 0x2b, 0x32, 0x84, 0x8f, 0x5b, 0xe7, 0xcc, 0x3f, 0xe2,
+  0x2c, 0x67, 0x86, 0x94, 0xf4, 0xb2, 0x2b, 0x6c, 0x52, 0x3b, 0x67, 0x2a,
+  0x8d, 0x58, 0x95, 0x00, 0x14, 0x46, 0x24, 0xac, 0x0b, 0xfa, 0xc9, 0x8e,
+  0xc7, 0x26, 0x80, 0xdf, 0xd1, 0xe1, 0x97, 0xe3, 0xf8, 0xbb, 0x68, 0xc6,
+  0x9c, 0xbd, 0xbe, 0x08, 0x54, 0x3b, 0x10, 0x32, 0x7c, 0x81, 0x1f, 0x2b,
+  0x28, 0x95, 0xa8, 0x41, 0x0a, 0xc6, 0xd0, 0x30, 0x66, 0xb4, 0xe9, 0xf2,
+  0xa2, 0x00, 0x69, 0x20, 0x07, 0xca, 0x82, 0x4c, 0x1e, 0xcf, 0xa7, 0x98,
+  0xb8, 0x0c, 0xee, 0xcd, 0x16, 0x1c, 0xbe, 0x1a, 0x63, 0xd4, 0xc0, 0x99,
+  0xf6, 0x67, 0xb2, 0xf0, 0x8e, 0x17, 0x2d, 0x58, 0xc2, 0x80, 0xaa, 0x5d,
+  0x96, 0xc7, 0xb3, 0x28, 0xed, 0xf0, 0xda, 0x8e, 0xb6, 0x47, 0x1b, 0x8f,
+  0x4e, 0x15, 0xf1, 0x97, 0x4c, 0x0b, 0x4b, 0xaf, 0x81, 0xd4, 0x46, 0x94,
+  0x62, 0x2c, 0x43, 0xa7, 0x3c, 0x25, 0x48, 0x19, 0x63, 0xf2, 0x5c, 0xaa,
+  0x15, 0x89, 0x76, 0x84, 0x85, 0x73, 0x91, 0x7d, 0x28, 0x3c, 0x09, 0x83,
+  0x82, 0xbc, 0xf7,
+};
+
+#if 0
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            03:37:b9:28:34:7c:60:a6:ae:c5:ad:b1:21:7f:38:60
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+        Validity
+            Not Before: Nov  9 12:00:00 2007 GMT
+            Not After : Nov 10 00:00:00 2021 GMT
+        Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:f3:96:62:d8:75:6e:19:ff:3f:34:7c:49:4f:31:
+                    7e:0d:04:4e:99:81:e2:b3:85:55:91:30:b1:c0:af:
+                    70:bb:2c:a8:e7:18:aa:3f:78:f7:90:68:52:86:01:
+                    88:97:e2:3b:06:65:90:aa:bd:65:76:c2:ec:be:10:
+                    5b:37:78:83:60:75:45:c6:bd:74:aa:b6:9f:a4:3a:
+                    01:50:17:c4:39:69:b9:f1:4f:ef:82:c1:ca:f3:4a:
+                    db:cc:9e:50:4f:4d:40:a3:3a:90:e7:86:66:bc:f0:
+                    3e:76:28:4c:d1:75:80:9e:6a:35:14:35:03:9e:db:
+                    0c:8c:c2:28:ad:50:b2:ce:f6:91:a3:c3:a5:0a:58:
+                    49:f6:75:44:6c:ba:f9:ce:e9:ab:3a:02:e0:4d:f3:
+                    ac:e2:7a:e0:60:22:05:3c:82:d3:52:e2:f3:9c:47:
+                    f8:3b:d8:b2:4b:93:56:4a:bf:70:ab:3e:e9:68:c8:
+                    1d:8f:58:1d:2a:4d:5e:27:3d:ad:0a:59:2f:5a:11:
+                    20:40:d9:68:04:68:2d:f4:c0:84:0b:0a:1b:78:df:
+                    ed:1a:58:dc:fb:41:5a:6d:6b:f2:ed:1c:ee:5c:32:
+                    b6:5c:ec:d7:a6:03:32:a6:e8:de:b7:28:27:59:88:
+                    80:ff:7b:ad:89:58:d5:1e:14:a4:f2:b0:70:d4:a0:
+                    3e:a7
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Key Usage: critical
+                Digital Signature, Certificate Sign, CRL Sign
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
+            X509v3 Certificate Policies: 
+                Policy: 2.16.840.1.114412.2.1
+                  CPS: http://www.digicert.com/ssl-cps-repository.htm
+                  User Notice:
+                    Explicit Text: 
+
+            X509v3 Basic Constraints: critical
+                CA:TRUE, pathlen:0
+            Authority Information Access: 
+                OCSP - URI:http://ocsp.digicert.com
+                CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
+
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+                Full Name:
+                  URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+            X509v3 Subject Key Identifier: 
+                4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+            X509v3 Authority Key Identifier: 
+                keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+    Signature Algorithm: sha1WithRSAEncryption
+         4c:7a:17:87:28:5d:17:bc:b2:32:73:bf:cd:2e:f5:58:31:1d:
+         f0:b1:71:54:9c:d6:9b:67:93:db:2f:03:3e:16:6f:1e:03:c9:
+         53:84:a3:56:60:1e:78:94:1b:a2:a8:6f:a3:a4:8b:52:91:d7:
+         dd:5c:95:bb:ef:b5:16:49:e9:a5:42:4f:34:f2:47:ff:ae:81:
+         7f:13:54:b7:20:c4:70:15:cb:81:0a:81:cb:74:57:dc:9c:df:
+         24:a4:29:0c:18:f0:1c:e4:ae:07:33:ec:f1:49:3e:55:cf:6e:
+         4f:0d:54:7b:d3:c9:e8:15:48:d4:c5:bb:dc:35:1c:77:45:07:
+         48:45:85:bd:d7:7e:53:b8:c0:16:d9:95:cd:8b:8d:7d:c9:60:
+         4f:d1:a2:9b:e3:d0:30:d6:b4:73:36:e6:d2:f9:03:b2:e3:a4:
+         f5:e5:b8:3e:04:49:00:ba:2e:a6:4a:72:83:72:9d:f7:0b:8c:
+         a9:89:e7:b3:d7:64:1f:d6:e3:60:cb:03:c4:dc:88:e9:9d:25:
+         01:00:71:cb:03:b4:29:60:25:8f:f9:46:d1:7b:71:ae:cd:53:
+         12:5b:84:8e:c2:0f:c7:ed:93:19:d9:c9:fa:8f:58:34:76:32:
+         2f:ae:e1:50:14:61:d4:a8:58:a3:c8:30:13:23:ef:c6:25:8c:
+         36:8f:1c:80
+-----BEGIN CERTIFICATE-----
+MIIG5jCCBc6gAwIBAgIQAze5KDR8YKauxa2xIX84YDANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA3MTEwOTEyMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/
+PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC
+7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw
+PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6
+4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo
+LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U
+pPKwcNSgPqcCAwEAAaOCA4UwggOBMA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy
+BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH
+AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wEgYDVR0TAQH/BAgwBgEB/wIBADCB
+gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
+dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2Vy
+dHMvRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcw
+gYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hB
+c3N1cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0
+LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYE
+FExYyyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoI
+Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBMeheHKF0XvLIyc7/NLvVYMR3wsXFU
+nNabZ5PbLwM+Fm8eA8lThKNWYB54lBuiqG+jpItSkdfdXJW777UWSemlQk808kf/
+roF/E1S3IMRwFcuBCoHLdFfcnN8kpCkMGPAc5K4HM+zxST5Vz25PDVR708noFUjU
+xbvcNRx3RQdIRYW9135TuMAW2ZXNi419yWBP0aKb49Aw1rRzNubS+QOy46T15bg+
+BEkAui6mSnKDcp33C4ypieez12Qf1uNgywPE3IjpnSUBAHHLA7QpYCWP+UbRe3Gu
+zVMSW4SOwg/H7ZMZ2cn6j1g0djIvruFQFGHUqFijyDATI+/GJYw2jxyA
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert101[] = {
+  0x30, 0x82, 0x06, 0xe6, 0x30, 0x82, 0x05, 0xce, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x10, 0x03, 0x37, 0xb9, 0x28, 0x34, 0x7c, 0x60, 0xa6, 0xae,
+  0xc5, 0xad, 0xb1, 0x21, 0x7f, 0x38, 0x60, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+  0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+  0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+  0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+  0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x31, 0x30, 0x39, 0x31, 0x32,
+  0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31,
+  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b,
+  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+  0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+  0x6d, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1f,
+  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+  0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+  0x45, 0x56, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30,
+  0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+  0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+  0x82, 0x01, 0x01, 0x00, 0xf3, 0x96, 0x62, 0xd8, 0x75, 0x6e, 0x19, 0xff,
+  0x3f, 0x34, 0x7c, 0x49, 0x4f, 0x31, 0x7e, 0x0d, 0x04, 0x4e, 0x99, 0x81,
+  0xe2, 0xb3, 0x85, 0x55, 0x91, 0x30, 0xb1, 0xc0, 0xaf, 0x70, 0xbb, 0x2c,
+  0xa8, 0xe7, 0x18, 0xaa, 0x3f, 0x78, 0xf7, 0x90, 0x68, 0x52, 0x86, 0x01,
+  0x88, 0x97, 0xe2, 0x3b, 0x06, 0x65, 0x90, 0xaa, 0xbd, 0x65, 0x76, 0xc2,
+  0xec, 0xbe, 0x10, 0x5b, 0x37, 0x78, 0x83, 0x60, 0x75, 0x45, 0xc6, 0xbd,
+  0x74, 0xaa, 0xb6, 0x9f, 0xa4, 0x3a, 0x01, 0x50, 0x17, 0xc4, 0x39, 0x69,
+  0xb9, 0xf1, 0x4f, 0xef, 0x82, 0xc1, 0xca, 0xf3, 0x4a, 0xdb, 0xcc, 0x9e,
+  0x50, 0x4f, 0x4d, 0x40, 0xa3, 0x3a, 0x90, 0xe7, 0x86, 0x66, 0xbc, 0xf0,
+  0x3e, 0x76, 0x28, 0x4c, 0xd1, 0x75, 0x80, 0x9e, 0x6a, 0x35, 0x14, 0x35,
+  0x03, 0x9e, 0xdb, 0x0c, 0x8c, 0xc2, 0x28, 0xad, 0x50, 0xb2, 0xce, 0xf6,
+  0x91, 0xa3, 0xc3, 0xa5, 0x0a, 0x58, 0x49, 0xf6, 0x75, 0x44, 0x6c, 0xba,
+  0xf9, 0xce, 0xe9, 0xab, 0x3a, 0x02, 0xe0, 0x4d, 0xf3, 0xac, 0xe2, 0x7a,
+  0xe0, 0x60, 0x22, 0x05, 0x3c, 0x82, 0xd3, 0x52, 0xe2, 0xf3, 0x9c, 0x47,
+  0xf8, 0x3b, 0xd8, 0xb2, 0x4b, 0x93, 0x56, 0x4a, 0xbf, 0x70, 0xab, 0x3e,
+  0xe9, 0x68, 0xc8, 0x1d, 0x8f, 0x58, 0x1d, 0x2a, 0x4d, 0x5e, 0x27, 0x3d,
+  0xad, 0x0a, 0x59, 0x2f, 0x5a, 0x11, 0x20, 0x40, 0xd9, 0x68, 0x04, 0x68,
+  0x2d, 0xf4, 0xc0, 0x84, 0x0b, 0x0a, 0x1b, 0x78, 0xdf, 0xed, 0x1a, 0x58,
+  0xdc, 0xfb, 0x41, 0x5a, 0x6d, 0x6b, 0xf2, 0xed, 0x1c, 0xee, 0x5c, 0x32,
+  0xb6, 0x5c, 0xec, 0xd7, 0xa6, 0x03, 0x32, 0xa6, 0xe8, 0xde, 0xb7, 0x28,
+  0x27, 0x59, 0x88, 0x80, 0xff, 0x7b, 0xad, 0x89, 0x58, 0xd5, 0x1e, 0x14,
+  0xa4, 0xf2, 0xb0, 0x70, 0xd4, 0xa0, 0x3e, 0xa7, 0x02, 0x03, 0x01, 0x00,
+  0x01, 0xa3, 0x82, 0x03, 0x85, 0x30, 0x82, 0x03, 0x81, 0x30, 0x0e, 0x06,
+  0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+  0x86, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x34, 0x30, 0x32,
+  0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+  0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+  0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x03, 0x08, 0x30, 0x82, 0x01, 0xc4, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+  0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0xb7, 0x30, 0x82, 0x01, 0xb3, 0x06,
+  0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6c, 0x02, 0x01, 0x30, 0x82,
+  0x01, 0xa4, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+  0x02, 0x01, 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+  0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d,
+  0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68,
+  0x74, 0x6d, 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+  0x05, 0x07, 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52,
+  0x00, 0x41, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73,
+  0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+  0x00, 0x68, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65,
+  0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63,
+  0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f,
+  0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75,
+  0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63,
+  0x00, 0x63, 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e,
+  0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20,
+  0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69,
+  0x00, 0x67, 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74,
+  0x00, 0x20, 0x00, 0x45, 0x00, 0x56, 0x00, 0x20, 0x00, 0x43, 0x00, 0x50,
+  0x00, 0x53, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+  0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65,
+  0x00, 0x6c, 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20,
+  0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20,
+  0x00, 0x41, 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d,
+  0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68,
+  0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+  0x00, 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+  0x00, 0x61, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74,
+  0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+  0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e,
+  0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72,
+  0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68,
+  0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20,
+  0x00, 0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66,
+  0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65,
+  0x00, 0x2e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+  0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x81,
+  0x83, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+  0x77, 0x30, 0x75, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+  0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+  0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+  0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+  0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a,
+  0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65,
+  0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x41, 0x43, 0x65, 0x72,
+  0x74, 0x73, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48,
+  0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65,
+  0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+  0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30,
+  0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74,
+  0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69,
+  0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44,
+  0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41,
+  0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f,
+  0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e,
+  0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+  0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74,
+  0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72,
+  0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e,
+  0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63,
+  0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
+  0x14, 0x4c, 0x58, 0xcb, 0x25, 0xf0, 0x41, 0x4f, 0x52, 0xf4, 0x28, 0xc8,
+  0x81, 0x43, 0x9b, 0xa6, 0xa8, 0xa0, 0xe6, 0x92, 0xe5, 0x30, 0x1f, 0x06,
+  0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e,
+  0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08,
+  0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+  0x01, 0x00, 0x4c, 0x7a, 0x17, 0x87, 0x28, 0x5d, 0x17, 0xbc, 0xb2, 0x32,
+  0x73, 0xbf, 0xcd, 0x2e, 0xf5, 0x58, 0x31, 0x1d, 0xf0, 0xb1, 0x71, 0x54,
+  0x9c, 0xd6, 0x9b, 0x67, 0x93, 0xdb, 0x2f, 0x03, 0x3e, 0x16, 0x6f, 0x1e,
+  0x03, 0xc9, 0x53, 0x84, 0xa3, 0x56, 0x60, 0x1e, 0x78, 0x94, 0x1b, 0xa2,
+  0xa8, 0x6f, 0xa3, 0xa4, 0x8b, 0x52, 0x91, 0xd7, 0xdd, 0x5c, 0x95, 0xbb,
+  0xef, 0xb5, 0x16, 0x49, 0xe9, 0xa5, 0x42, 0x4f, 0x34, 0xf2, 0x47, 0xff,
+  0xae, 0x81, 0x7f, 0x13, 0x54, 0xb7, 0x20, 0xc4, 0x70, 0x15, 0xcb, 0x81,
+  0x0a, 0x81, 0xcb, 0x74, 0x57, 0xdc, 0x9c, 0xdf, 0x24, 0xa4, 0x29, 0x0c,
+  0x18, 0xf0, 0x1c, 0xe4, 0xae, 0x07, 0x33, 0xec, 0xf1, 0x49, 0x3e, 0x55,
+  0xcf, 0x6e, 0x4f, 0x0d, 0x54, 0x7b, 0xd3, 0xc9, 0xe8, 0x15, 0x48, 0xd4,
+  0xc5, 0xbb, 0xdc, 0x35, 0x1c, 0x77, 0x45, 0x07, 0x48, 0x45, 0x85, 0xbd,
+  0xd7, 0x7e, 0x53, 0xb8, 0xc0, 0x16, 0xd9, 0x95, 0xcd, 0x8b, 0x8d, 0x7d,
+  0xc9, 0x60, 0x4f, 0xd1, 0xa2, 0x9b, 0xe3, 0xd0, 0x30, 0xd6, 0xb4, 0x73,
+  0x36, 0xe6, 0xd2, 0xf9, 0x03, 0xb2, 0xe3, 0xa4, 0xf5, 0xe5, 0xb8, 0x3e,
+  0x04, 0x49, 0x00, 0xba, 0x2e, 0xa6, 0x4a, 0x72, 0x83, 0x72, 0x9d, 0xf7,
+  0x0b, 0x8c, 0xa9, 0x89, 0xe7, 0xb3, 0xd7, 0x64, 0x1f, 0xd6, 0xe3, 0x60,
+  0xcb, 0x03, 0xc4, 0xdc, 0x88, 0xe9, 0x9d, 0x25, 0x01, 0x00, 0x71, 0xcb,
+  0x03, 0xb4, 0x29, 0x60, 0x25, 0x8f, 0xf9, 0x46, 0xd1, 0x7b, 0x71, 0xae,
+  0xcd, 0x53, 0x12, 0x5b, 0x84, 0x8e, 0xc2, 0x0f, 0xc7, 0xed, 0x93, 0x19,
+  0xd9, 0xc9, 0xfa, 0x8f, 0x58, 0x34, 0x76, 0x32, 0x2f, 0xae, 0xe1, 0x50,
+  0x14, 0x61, 0xd4, 0xa8, 0x58, 0xa3, 0xc8, 0x30, 0x13, 0x23, 0xef, 0xc6,
+  0x25, 0x8c, 0x36, 0x8f, 0x1c, 0x80,
+};
diff --git a/net/quic/crypto/common_cert_set_test.cc b/net/quic/crypto/common_cert_set_test.cc
new file mode 100644
index 0000000..e15a10e
--- /dev/null
+++ b/net/quic/crypto/common_cert_set_test.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/common_cert_set.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+static const unsigned char kGIACertificate[] = {
+  0x30, 0x82, 0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03, 0x02, 0x01,
+  0x02, 0x02, 0x03, 0x0b, 0x67, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+  0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+  0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+  0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+  0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+  0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+  0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+  0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30,
+  0x36, 0x30, 0x38, 0x32, 0x30, 0x34, 0x33, 0x32, 0x37, 0x5a, 0x17, 0x0d,
+  0x31, 0x33, 0x30, 0x36, 0x30, 0x37, 0x31, 0x39, 0x34, 0x33, 0x32, 0x37,
+  0x5a, 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+  0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+  0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+  0x63, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+  0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+  0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+  0x79, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+  0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+  0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xc9, 0xed, 0xb7, 0xa4, 0x8b, 0x9c,
+  0x57, 0xe7, 0x84, 0x3e, 0x40, 0x7d, 0x84, 0xf4, 0x8f, 0xd1, 0x71, 0x63,
+  0x53, 0x99, 0xe7, 0x79, 0x74, 0x14, 0xaf, 0x44, 0x99, 0x33, 0x20, 0x92,
+  0x8d, 0x7b, 0xe5, 0x28, 0x0c, 0xba, 0xad, 0x6c, 0x49, 0x7e, 0x83, 0x5f,
+  0x34, 0x59, 0x4e, 0x0a, 0x7a, 0x30, 0xcd, 0xd0, 0xd7, 0xc4, 0x57, 0x45,
+  0xed, 0xd5, 0xaa, 0xd6, 0x73, 0x26, 0xce, 0xad, 0x32, 0x13, 0xb8, 0xd7,
+  0x0f, 0x1d, 0x3b, 0xdf, 0xdd, 0xdc, 0x08, 0x36, 0xa8, 0x6f, 0x51, 0x44,
+  0x9b, 0xca, 0xd6, 0x20, 0x52, 0x73, 0xb7, 0x26, 0x87, 0x35, 0x6a, 0xdb,
+  0xa9, 0xe5, 0xd4, 0x59, 0xa5, 0x2b, 0xfc, 0x67, 0x19, 0x39, 0xfa, 0x93,
+  0x18, 0x18, 0x6c, 0xde, 0xdd, 0x25, 0x8a, 0x0e, 0x33, 0x14, 0x47, 0xc2,
+  0xef, 0x01, 0x50, 0x79, 0xe4, 0xfd, 0x69, 0xd1, 0xa7, 0xc0, 0xac, 0xe2,
+  0x57, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa3, 0x30, 0x81,
+  0xa0, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+  0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+  0x04, 0x16, 0x04, 0x14, 0xbf, 0xc0, 0x30, 0xeb, 0xf5, 0x43, 0x11, 0x3e,
+  0x67, 0xba, 0x9e, 0x91, 0xfb, 0xfc, 0x6a, 0xda, 0xe3, 0x6b, 0x12, 0x24,
+  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+  0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8,
+  0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x12, 0x06,
+  0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+  0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+  0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29,
+  0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67,
+  0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+  0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
+  0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+  0xb8, 0x8a, 0x23, 0xc6, 0x48, 0x96, 0xb1, 0x11, 0x7c, 0x60, 0x77, 0x5e,
+  0x05, 0x9a, 0xab, 0xa1, 0xc6, 0xfa, 0x82, 0x1c, 0x18, 0x07, 0xc4, 0xeb,
+  0x81, 0xb0, 0xa8, 0x66, 0xeb, 0x49, 0xa8, 0xe9, 0x0c, 0xd3, 0x29, 0xad,
+  0xf5, 0xef, 0x24, 0x4c, 0xfd, 0xe4, 0x4b, 0xca, 0x7f, 0x5e, 0x63, 0xab,
+  0x99, 0x27, 0xcb, 0x9f, 0x36, 0x21, 0x2c, 0xb9, 0x10, 0x60, 0x67, 0xcd,
+  0xd2, 0xb4, 0xf0, 0xf0, 0xab, 0x71, 0xe5, 0x8b, 0x5a, 0x89, 0x27, 0x11,
+  0x84, 0xaa, 0x8e, 0xbf, 0x99, 0xf0, 0x9d, 0x09, 0x21, 0x0a, 0x52, 0x19,
+  0x9a, 0x5a, 0x09, 0xd2, 0x90, 0xb7, 0xfa, 0x0c, 0xf8, 0x7e, 0x78, 0xa2,
+  0xb0, 0x85, 0xaf, 0x5c, 0x4c, 0x99, 0xd9, 0x5c, 0x55, 0x29, 0xf9, 0xa5,
+  0x51, 0x42, 0x2e, 0x3a, 0xcb, 0x38, 0x8c, 0x78, 0x3b, 0xcb, 0xf8, 0xfb,
+  0x95, 0x87, 0xbc, 0xbc, 0x90, 0xf9, 0x50, 0x32,
+};
+
+TEST(CommonCertSets, FindGIA) {
+  StringPiece gia(reinterpret_cast<const char*>(kGIACertificate),
+                  sizeof(kGIACertificate));
+
+  const CommonCertSets* sets(CommonCertSets::GetInstanceQUIC());
+
+  const uint64 in_hash = GG_UINT64_C(0xc9fef74053f99f39);
+  uint64 hash;
+  uint32 index;
+  ASSERT_TRUE(sets->MatchCert(
+      gia,
+      StringPiece(reinterpret_cast<const char*>(&in_hash), sizeof(in_hash)),
+      &hash, &index));
+  EXPECT_EQ(in_hash, hash);
+
+  StringPiece gia_copy = sets->GetCert(hash, index);
+  EXPECT_FALSE(gia_copy.empty());
+  ASSERT_EQ(gia.size(), gia_copy.size());
+  EXPECT_EQ(0, memcmp(gia.data(), gia_copy.data(), gia.size()));
+}
+
+TEST(CommonCertSets, NonMatch) {
+  const CommonCertSets* sets(CommonCertSets::GetInstanceQUIC());
+  StringPiece not_a_cert("hello");
+  const uint64 in_hash = GG_UINT64_C(0xc9fef74053f99f39);
+  uint64 hash;
+  uint32 index;
+  EXPECT_FALSE(sets->MatchCert(
+      not_a_cert,
+      StringPiece(reinterpret_cast<const char*>(&in_hash), sizeof(in_hash)),
+      &hash, &index));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/crypto_framer.cc b/net/quic/crypto/crypto_framer.cc
new file mode 100644
index 0000000..bc5c8b7
--- /dev/null
+++ b/net/quic/crypto/crypto_framer.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_framer.h"
+
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_data_writer.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+const size_t kQuicTagSize = sizeof(uint32);
+const size_t kCryptoEndOffsetSize = sizeof(uint32);
+const size_t kNumEntriesSize = sizeof(uint16);
+
+// OneShotVisitor is a framer visitor that records a single handshake message.
+class OneShotVisitor : public CryptoFramerVisitorInterface {
+ public:
+  OneShotVisitor() : error_(false) {}
+
+  virtual void OnError(CryptoFramer* framer) OVERRIDE { error_ = true; }
+
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE {
+    out_.reset(new CryptoHandshakeMessage(message));
+  }
+
+  bool error() const { return error_; }
+
+  CryptoHandshakeMessage* release() { return out_.release(); }
+
+ private:
+  scoped_ptr<CryptoHandshakeMessage> out_;
+  bool error_;
+};
+
+}  // namespace
+
+CryptoFramer::CryptoFramer()
+    : visitor_(nullptr),
+      num_entries_(0),
+      values_len_(0) {
+  Clear();
+}
+
+CryptoFramer::~CryptoFramer() {}
+
+// static
+CryptoHandshakeMessage* CryptoFramer::ParseMessage(StringPiece in) {
+  OneShotVisitor visitor;
+  CryptoFramer framer;
+
+  framer.set_visitor(&visitor);
+  if (!framer.ProcessInput(in) || visitor.error() ||
+      framer.InputBytesRemaining()) {
+    return nullptr;
+  }
+
+  return visitor.release();
+}
+
+bool CryptoFramer::ProcessInput(StringPiece input) {
+  DCHECK_EQ(QUIC_NO_ERROR, error_);
+  if (error_ != QUIC_NO_ERROR) {
+    return false;
+  }
+  error_ = Process(input);
+  if (error_ != QUIC_NO_ERROR) {
+    visitor_->OnError(this);
+    return false;
+  }
+
+  return true;
+}
+
+// static
+QuicData* CryptoFramer::ConstructHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  size_t num_entries = message.tag_value_map().size();
+  size_t pad_length = 0;
+  bool need_pad_tag = false;
+  bool need_pad_value = false;
+
+  size_t len = message.size();
+  if (len < message.minimum_size()) {
+    need_pad_tag = true;
+    need_pad_value = true;
+    num_entries++;
+
+    size_t delta = message.minimum_size() - len;
+    const size_t overhead = kQuicTagSize + kCryptoEndOffsetSize;
+    if (delta > overhead) {
+      pad_length = delta - overhead;
+    }
+    len += overhead + pad_length;
+  }
+
+  if (num_entries > kMaxEntries) {
+    return nullptr;
+  }
+
+
+  QuicDataWriter writer(len);
+  if (!writer.WriteUInt32(message.tag())) {
+    DCHECK(false) << "Failed to write message tag.";
+    return nullptr;
+  }
+  if (!writer.WriteUInt16(num_entries)) {
+    DCHECK(false) << "Failed to write size.";
+    return nullptr;
+  }
+  if (!writer.WriteUInt16(0)) {
+    DCHECK(false) << "Failed to write padding.";
+    return nullptr;
+  }
+
+  uint32 end_offset = 0;
+  // Tags and offsets
+  for (QuicTagValueMap::const_iterator it = message.tag_value_map().begin();
+       it != message.tag_value_map().end(); ++it) {
+    if (it->first == kPAD && need_pad_tag) {
+      // Existing PAD tags are only checked when padding needs to be added
+      // because parts of the code may need to reserialize received messages
+      // and those messages may, legitimately include padding.
+      DCHECK(false) << "Message needed padding but already contained a PAD tag";
+      return nullptr;
+    }
+
+    if (it->first > kPAD && need_pad_tag) {
+      need_pad_tag = false;
+      if (!WritePadTag(&writer, pad_length, &end_offset)) {
+        return nullptr;
+      }
+    }
+
+    if (!writer.WriteUInt32(it->first)) {
+      DCHECK(false) << "Failed to write tag.";
+      return nullptr;
+    }
+    end_offset += it->second.length();
+    if (!writer.WriteUInt32(end_offset)) {
+      DCHECK(false) << "Failed to write end offset.";
+      return nullptr;
+    }
+  }
+
+  if (need_pad_tag) {
+    if (!WritePadTag(&writer, pad_length, &end_offset)) {
+      return nullptr;
+    }
+  }
+
+  // Values
+  for (QuicTagValueMap::const_iterator it = message.tag_value_map().begin();
+       it != message.tag_value_map().end(); ++it) {
+    if (it->first > kPAD && need_pad_value) {
+      need_pad_value = false;
+      if (!writer.WriteRepeatedByte('-', pad_length)) {
+        DCHECK(false) << "Failed to write padding.";
+        return nullptr;
+      }
+    }
+
+    if (!writer.WriteBytes(it->second.data(), it->second.length())) {
+      DCHECK(false) << "Failed to write value.";
+      return nullptr;
+    }
+  }
+
+  if (need_pad_value) {
+    if (!writer.WriteRepeatedByte('-', pad_length)) {
+      DCHECK(false) << "Failed to write padding.";
+      return nullptr;
+    }
+  }
+
+  return new QuicData(writer.take(), len, true);
+}
+
+void CryptoFramer::Clear() {
+  message_.Clear();
+  tags_and_lengths_.clear();
+  error_ = QUIC_NO_ERROR;
+  state_ = STATE_READING_TAG;
+}
+
+QuicErrorCode CryptoFramer::Process(StringPiece input) {
+  // Add this data to the buffer.
+  buffer_.append(input.data(), input.length());
+  QuicDataReader reader(buffer_.data(), buffer_.length());
+
+  switch (state_) {
+    case STATE_READING_TAG:
+      if (reader.BytesRemaining() < kQuicTagSize) {
+        break;
+      }
+      QuicTag message_tag;
+      reader.ReadUInt32(&message_tag);
+      message_.set_tag(message_tag);
+      state_ = STATE_READING_NUM_ENTRIES;
+    case STATE_READING_NUM_ENTRIES:
+      if (reader.BytesRemaining() < kNumEntriesSize + sizeof(uint16)) {
+        break;
+      }
+      reader.ReadUInt16(&num_entries_);
+      if (num_entries_ > kMaxEntries) {
+        return QUIC_CRYPTO_TOO_MANY_ENTRIES;
+      }
+      uint16 padding;
+      reader.ReadUInt16(&padding);
+
+      tags_and_lengths_.reserve(num_entries_);
+      state_ = STATE_READING_TAGS_AND_LENGTHS;
+      values_len_ = 0;
+    case STATE_READING_TAGS_AND_LENGTHS: {
+      if (reader.BytesRemaining() <
+              num_entries_ * (kQuicTagSize + kCryptoEndOffsetSize)) {
+        break;
+      }
+
+      uint32 last_end_offset = 0;
+      for (unsigned i = 0; i < num_entries_; ++i) {
+        QuicTag tag;
+        reader.ReadUInt32(&tag);
+        if (i > 0 && tag <= tags_and_lengths_[i-1].first) {
+          if (tag == tags_and_lengths_[i-1].first) {
+            return QUIC_CRYPTO_DUPLICATE_TAG;
+          }
+          return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
+        }
+
+        uint32 end_offset;
+        reader.ReadUInt32(&end_offset);
+
+        if (end_offset < last_end_offset) {
+          return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
+        }
+        tags_and_lengths_.push_back(
+            make_pair(tag, static_cast<size_t>(end_offset - last_end_offset)));
+        last_end_offset = end_offset;
+      }
+      values_len_ = last_end_offset;
+      state_ = STATE_READING_VALUES;
+    }
+    case STATE_READING_VALUES:
+      if (reader.BytesRemaining() < values_len_) {
+        break;
+      }
+      for (vector<pair<QuicTag, size_t> >::const_iterator
+           it = tags_and_lengths_.begin(); it != tags_and_lengths_.end();
+           it++) {
+        StringPiece value;
+        reader.ReadStringPiece(&value, it->second);
+        message_.SetStringPiece(it->first, value);
+      }
+      visitor_->OnHandshakeMessage(message_);
+      Clear();
+      state_ = STATE_READING_TAG;
+      break;
+  }
+  // Save any remaining data.
+  buffer_ = reader.PeekRemainingPayload().as_string();
+  return QUIC_NO_ERROR;
+}
+
+// static
+bool CryptoFramer::WritePadTag(QuicDataWriter* writer,
+                               size_t pad_length,
+                               uint32* end_offset) {
+  if (!writer->WriteUInt32(kPAD)) {
+    DCHECK(false) << "Failed to write tag.";
+    return false;
+  }
+  *end_offset += pad_length;
+  if (!writer->WriteUInt32(*end_offset)) {
+    DCHECK(false) << "Failed to write end offset.";
+    return false;
+  }
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_framer.h b/net/quic/crypto/crypto_framer.h
new file mode 100644
index 0000000..a200fd6
--- /dev/null
+++ b/net/quic/crypto/crypto_framer.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
+#define NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CryptoFramer;
+class QuicData;
+class QuicDataReader;
+class QuicDataWriter;
+
+class NET_EXPORT_PRIVATE CryptoFramerVisitorInterface {
+ public:
+  virtual ~CryptoFramerVisitorInterface() {}
+
+  // Called if an error is detected.
+  virtual void OnError(CryptoFramer* framer) = 0;
+
+  // Called when a complete handshake message has been parsed.
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) = 0;
+};
+
+// A class for framing the crypto messages that are exchanged in a QUIC
+// session.
+class NET_EXPORT_PRIVATE CryptoFramer {
+ public:
+  CryptoFramer();
+
+  virtual ~CryptoFramer();
+
+  // ParseMessage parses exactly one message from the given StringPiece. If
+  // there is an error, the message is truncated, or the message has trailing
+  // garbage then nullptr will be returned.
+  static CryptoHandshakeMessage* ParseMessage(base::StringPiece in);
+
+  // Set callbacks to be called from the framer.  A visitor must be set, or
+  // else the framer will crash.  It is acceptable for the visitor to do
+  // nothing.  If this is called multiple times, only the last visitor
+  // will be used.  |visitor| will be owned by the framer.
+  void set_visitor(CryptoFramerVisitorInterface* visitor) {
+    visitor_ = visitor;
+  }
+
+  QuicErrorCode error() const { return error_; }
+
+  // Processes input data, which must be delivered in order. Returns
+  // false if there was an error, and true otherwise.
+  bool ProcessInput(base::StringPiece input);
+
+  // Returns the number of bytes of buffered input data remaining to be
+  // parsed.
+  size_t InputBytesRemaining() const { return buffer_.length(); }
+
+  // Returns a new QuicData owned by the caller that contains a serialized
+  // |message|, or nullptr if there was an error.
+  static QuicData* ConstructHandshakeMessage(
+      const CryptoHandshakeMessage& message);
+
+ private:
+  // Clears per-message state.  Does not clear the visitor.
+  void Clear();
+
+  // Process does does the work of |ProcessInput|, but returns an error code,
+  // doesn't set error_ and doesn't call |visitor_->OnError()|.
+  QuicErrorCode Process(base::StringPiece input);
+
+  static bool WritePadTag(QuicDataWriter* writer,
+                          size_t pad_length,
+                          uint32* end_offset);
+
+  // Represents the current state of the parsing state machine.
+  enum CryptoFramerState {
+    STATE_READING_TAG,
+    STATE_READING_NUM_ENTRIES,
+    STATE_READING_TAGS_AND_LENGTHS,
+    STATE_READING_VALUES
+  };
+
+  // Visitor to invoke when messages are parsed.
+  CryptoFramerVisitorInterface* visitor_;
+  // Last error.
+  QuicErrorCode error_;
+  // Remaining unparsed data.
+  std::string buffer_;
+  // Current state of the parsing.
+  CryptoFramerState state_;
+  // The message currently being parsed.
+  CryptoHandshakeMessage message_;
+  // Number of entires in the message currently being parsed.
+  uint16 num_entries_;
+  // tags_and_lengths_ contains the tags that are currently being parsed and
+  // their lengths.
+  std::vector<std::pair<QuicTag, size_t> > tags_and_lengths_;
+  // Cumulative length of all values in the message currently being parsed.
+  size_t values_len_;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
diff --git a/net/quic/crypto/crypto_framer_test.cc b/net/quic/crypto/crypto_framer_test.cc
new file mode 100644
index 0000000..01e9157
--- /dev/null
+++ b/net/quic/crypto/crypto_framer_test.cc
@@ -0,0 +1,485 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+char* AsChars(unsigned char* data) { return reinterpret_cast<char*>(data); }
+
+}  // namespace
+
+namespace test {
+
+class TestCryptoVisitor : public ::net::CryptoFramerVisitorInterface {
+ public:
+  TestCryptoVisitor() : error_count_(0) {}
+
+  virtual void OnError(CryptoFramer* framer) OVERRIDE {
+    DLOG(ERROR) << "CryptoFramer Error: " << framer->error();
+    ++error_count_;
+  }
+
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE {
+    messages_.push_back(message);
+  }
+
+  // Counters from the visitor callbacks.
+  int error_count_;
+
+  vector<CryptoHandshakeMessage> messages_;
+};
+
+TEST(CryptoFramerTest, ConstructHandshakeMessage) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  message.SetStringPiece(0x12345678, "abcdef");
+  message.SetStringPiece(0x12345679, "ghijk");
+  message.SetStringPiece(0x1234567A, "lmnopqr");
+
+  unsigned char packet[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x03, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x06, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x0b, 0x00, 0x00, 0x00,
+    // tag 3
+    0x7A, 0x56, 0x34, 0x12,
+    // end offset 3
+    0x12, 0x00, 0x00, 0x00,
+    // value 1
+    'a',  'b',  'c',  'd',
+    'e',  'f',
+    // value 2
+    'g',  'h',  'i',  'j',
+    'k',
+    // value 3
+    'l',  'm',  'n',  'o',
+    'p',  'q',  'r',
+  };
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  ASSERT_TRUE(data.get() != nullptr);
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageWithTwoKeys) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  message.SetStringPiece(0x12345678, "abcdef");
+  message.SetStringPiece(0x12345679, "ghijk");
+
+  unsigned char packet[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x06, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x0b, 0x00, 0x00, 0x00,
+    // value 1
+    'a',  'b',  'c',  'd',
+    'e',  'f',
+    // value 2
+    'g',  'h',  'i',  'j',
+    'k',
+  };
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  ASSERT_TRUE(data.get() != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageZeroLength) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  message.SetStringPiece(0x12345678, "");
+
+  unsigned char packet[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x01, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x00, 0x00, 0x00, 0x00,
+  };
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  ASSERT_TRUE(data.get() != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageTooManyEntries) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  for (uint32 key = 1; key <= kMaxEntries + 1; ++key) {
+    message.SetStringPiece(key, "abcdef");
+  }
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  EXPECT_TRUE(data.get() == nullptr);
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSize) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  message.SetStringPiece(0x01020304, "test");
+  message.set_minimum_size(64);
+
+  unsigned char packet[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    'P', 'A', 'D', 0,
+    // end offset 1
+    0x24, 0x00, 0x00, 0x00,
+    // tag 2
+    0x04, 0x03, 0x02, 0x01,
+    // end offset 2
+    0x28, 0x00, 0x00, 0x00,
+    // 36 bytes of padding.
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-',
+    // value 2
+    't', 'e', 's', 't',
+  };
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  ASSERT_TRUE(data.get() != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSizePadLast) {
+  CryptoHandshakeMessage message;
+  message.set_tag(0xFFAA7733);
+  message.SetStringPiece(1, "");
+  message.set_minimum_size(64);
+
+  unsigned char packet[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x01, 0x00, 0x00, 0x00,
+    // end offset 1
+    0x00, 0x00, 0x00, 0x00,
+    // tag 2
+    'P', 'A', 'D', 0,
+    // end offset 2
+    0x28, 0x00, 0x00, 0x00,
+    // 40 bytes of padding.
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+    '-', '-', '-', '-', '-', '-', '-', '-',
+  };
+
+  CryptoFramer framer;
+  scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+  ASSERT_TRUE(data.get() != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ProcessInput) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x06, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x0b, 0x00, 0x00, 0x00,
+    // value 1
+    'a',  'b',  'c',  'd',
+    'e',  'f',
+    // value 2
+    'g',  'h',  'i',  'j',
+    'k',
+  };
+
+  EXPECT_TRUE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(0u, framer.InputBytesRemaining());
+  EXPECT_EQ(0, visitor.error_count_);
+  ASSERT_EQ(1u, visitor.messages_.size());
+  const CryptoHandshakeMessage& message = visitor.messages_[0];
+  EXPECT_EQ(0xFFAA7733, message.tag());
+  EXPECT_EQ(2u, message.tag_value_map().size());
+  EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+  EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+}
+
+TEST(CryptoFramerTest, ProcessInputWithThreeKeys) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x03, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x06, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x0b, 0x00, 0x00, 0x00,
+    // tag 3
+    0x7A, 0x56, 0x34, 0x12,
+    // end offset 3
+    0x12, 0x00, 0x00, 0x00,
+    // value 1
+    'a',  'b',  'c',  'd',
+    'e',  'f',
+    // value 2
+    'g',  'h',  'i',  'j',
+    'k',
+    // value 3
+    'l',  'm',  'n',  'o',
+    'p',  'q',  'r',
+  };
+
+  EXPECT_TRUE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(0u, framer.InputBytesRemaining());
+  EXPECT_EQ(0, visitor.error_count_);
+  ASSERT_EQ(1u, visitor.messages_.size());
+  const CryptoHandshakeMessage& message = visitor.messages_[0];
+  EXPECT_EQ(0xFFAA7733, message.tag());
+  EXPECT_EQ(3u, message.tag_value_map().size());
+  EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+  EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+  EXPECT_EQ("lmnopqr", CryptoTestUtils::GetValueForTag(message, 0x1234567A));
+}
+
+TEST(CryptoFramerTest, ProcessInputIncrementally) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x06, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x0b, 0x00, 0x00, 0x00,
+    // value 1
+    'a',  'b',  'c',  'd',
+    'e',  'f',
+    // value 2
+    'g',  'h',  'i',  'j',
+    'k',
+  };
+
+  for (size_t i = 0; i < arraysize(input); i++) {
+    EXPECT_TRUE(framer.ProcessInput(StringPiece(AsChars(input) + i, 1)));
+  }
+  EXPECT_EQ(0u, framer.InputBytesRemaining());
+  ASSERT_EQ(1u, visitor.messages_.size());
+  const CryptoHandshakeMessage& message = visitor.messages_[0];
+  EXPECT_EQ(0xFFAA7733, message.tag());
+  EXPECT_EQ(2u, message.tag_value_map().size());
+  EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+  EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+}
+
+TEST(CryptoFramerTest, ProcessInputTagsOutOfOrder) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x13,
+    // end offset 1
+    0x01, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x02, 0x00, 0x00, 0x00,
+  };
+
+  EXPECT_FALSE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(QUIC_CRYPTO_TAGS_OUT_OF_ORDER, framer.error());
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessEndOffsetsOutOfOrder) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x01, 0x00, 0x00, 0x00,
+    // tag 2
+    0x78, 0x56, 0x34, 0x13,
+    // end offset 2
+    0x00, 0x00, 0x00, 0x00,
+  };
+
+  EXPECT_FALSE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(QUIC_CRYPTO_TAGS_OUT_OF_ORDER, framer.error());
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessInputTooManyEntries) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0xA0, 0x00,
+    // padding
+    0x00, 0x00,
+  };
+
+  EXPECT_FALSE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(QUIC_CRYPTO_TOO_MANY_ENTRIES, framer.error());
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessInputZeroLength) {
+  test::TestCryptoVisitor visitor;
+  CryptoFramer framer;
+  framer.set_visitor(&visitor);
+
+  unsigned char input[] = {
+    // tag
+    0x33, 0x77, 0xAA, 0xFF,
+    // num entries
+    0x02, 0x00,
+    // padding
+    0x00, 0x00,
+    // tag 1
+    0x78, 0x56, 0x34, 0x12,
+    // end offset 1
+    0x00, 0x00, 0x00, 0x00,
+    // tag 2
+    0x79, 0x56, 0x34, 0x12,
+    // end offset 2
+    0x05, 0x00, 0x00, 0x00,
+  };
+
+  EXPECT_TRUE(
+      framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+  EXPECT_EQ(0, visitor.error_count_);
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_handshake.cc b/net/quic/crypto/crypto_handshake.cc
new file mode 100644
index 0000000..5a928b6
--- /dev/null
+++ b/net/quic/crypto/crypto_handshake.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_handshake.h"
+
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/key_exchange.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+namespace net {
+
+QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters()
+    : key_exchange(0),
+      aead(0),
+      x509_ecdsa_supported(false) {
+}
+
+QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() {}
+
+CrypterPair::CrypterPair() {}
+
+CrypterPair::~CrypterPair() {}
+
+// static
+const char QuicCryptoConfig::kInitialLabel[] = "QUIC key expansion";
+
+// static
+const char QuicCryptoConfig::kCETVLabel[] = "QUIC CETV block";
+
+// static
+const char QuicCryptoConfig::kForwardSecureLabel[] =
+    "QUIC forward secure key expansion";
+
+QuicCryptoConfig::QuicCryptoConfig()
+    : common_cert_sets(CommonCertSets::GetInstanceQUIC()) {
+}
+
+QuicCryptoConfig::~QuicCryptoConfig() {}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_handshake.h b/net/quic/crypto/crypto_handshake.h
new file mode 100644
index 0000000..bfd2f34
--- /dev/null
+++ b/net/quic/crypto/crypto_handshake.h
@@ -0,0 +1,170 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
+#define NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CommonCertSets;
+class KeyExchange;
+class QuicDecrypter;
+class QuicEncrypter;
+
+// HandshakeFailureReason enum values are uploaded to UMA, they cannot be
+// changed.
+enum HandshakeFailureReason {
+  HANDSHAKE_OK = 0,
+
+  // Failure reasons for an invalid client nonce in CHLO.
+  //
+  // The default error value for nonce verification failures from strike
+  // register (covers old strike registers and unknown failures).
+  CLIENT_NONCE_UNKNOWN_FAILURE = 1,
+  // Client nonce had incorrect length.
+  CLIENT_NONCE_INVALID_FAILURE = 2,
+  // Client nonce is not unique.
+  CLIENT_NONCE_NOT_UNIQUE_FAILURE = 3,
+  // Client orbit is invalid or incorrect.
+  CLIENT_NONCE_INVALID_ORBIT_FAILURE = 4,
+  // Client nonce's timestamp is not in the strike register's valid time range.
+  CLIENT_NONCE_INVALID_TIME_FAILURE = 5,
+  // Strike register's RPC call timed out, client nonce couldn't be verified.
+  CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT = 6,
+  // Strike register is down, client nonce couldn't be verified.
+  CLIENT_NONCE_STRIKE_REGISTER_FAILURE = 7,
+
+  // Failure reasons for an invalid server nonce in CHLO.
+  //
+  // Unbox of server nonce failed.
+  SERVER_NONCE_DECRYPTION_FAILURE = 8,
+  // Decrypted server nonce had incorrect length.
+  SERVER_NONCE_INVALID_FAILURE = 9,
+  // Server nonce is not unique.
+  SERVER_NONCE_NOT_UNIQUE_FAILURE = 10,
+  // Server nonce's timestamp is not in the strike register's valid time range.
+  SERVER_NONCE_INVALID_TIME_FAILURE = 11,
+
+  // Failure reasons for an invalid server config in CHLO.
+  //
+  // Missing Server config id (kSCID) tag.
+  SERVER_CONFIG_INCHOATE_HELLO_FAILURE = 12,
+  // Couldn't find the Server config id (kSCID).
+  SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE = 13,
+
+  // Failure reasons for an invalid source-address token.
+  //
+  // Missing Source-address token (kSourceAddressTokenTag) tag.
+  SOURCE_ADDRESS_TOKEN_INVALID_FAILURE = 14,
+  // Unbox of Source-address token failed.
+  SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE = 15,
+  // Couldn't parse the unbox'ed Source-address token.
+  SOURCE_ADDRESS_TOKEN_PARSE_FAILURE = 16,
+  // Source-address token is for a different IP address.
+  SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE = 17,
+  // The source-address token has a timestamp in the future.
+  SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE = 18,
+  // The source-address token has expired.
+  SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE = 19,
+
+  MAX_FAILURE_REASON,
+};
+
+// These errors will be packed into an uint32 and we don't want to set the most
+// significant bit, which may be misinterpreted as the sign bit.
+COMPILE_ASSERT(MAX_FAILURE_REASON <= 32, failure_reason_out_of_sync);
+
+// A CrypterPair contains the encrypter and decrypter for an encryption level.
+struct NET_EXPORT_PRIVATE CrypterPair {
+  CrypterPair();
+  ~CrypterPair();
+  scoped_ptr<QuicEncrypter> encrypter;
+  scoped_ptr<QuicDecrypter> decrypter;
+};
+
+// Parameters negotiated by the crypto handshake.
+struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters {
+  // Initializes the members to 0 or empty values.
+  QuicCryptoNegotiatedParameters();
+  ~QuicCryptoNegotiatedParameters();
+
+  QuicTag key_exchange;
+  QuicTag aead;
+  std::string initial_premaster_secret;
+  std::string forward_secure_premaster_secret;
+  // subkey_secret is used as the PRK input to the HKDF used for key extraction.
+  std::string subkey_secret;
+  CrypterPair initial_crypters;
+  CrypterPair forward_secure_crypters;
+  // Normalized SNI: converted to lower case and trailing '.' removed.
+  std::string sni;
+  std::string client_nonce;
+  std::string server_nonce;
+  // hkdf_input_suffix contains the HKDF input following the label: the
+  // ConnectionId, client hello and server config. This is only populated in the
+  // client because only the client needs to derive the forward secure keys at a
+  // later time from the initial keys.
+  std::string hkdf_input_suffix;
+  // cached_certs contains the cached certificates that a client used when
+  // sending a client hello.
+  std::vector<std::string> cached_certs;
+  // client_key_exchange is used by clients to store the ephemeral KeyExchange
+  // for the connection.
+  scoped_ptr<KeyExchange> client_key_exchange;
+  // channel_id is set by servers to a ChannelID key when the client correctly
+  // proves possession of the corresponding private key. It consists of 32
+  // bytes of x coordinate, followed by 32 bytes of y coordinate. Both values
+  // are big-endian and the pair is a P-256 public key.
+  std::string channel_id;
+
+  // Used when generating proof signature when sending server config updates.
+  bool x509_ecdsa_supported;
+
+  // Used to generate cert chain when sending server config updates.
+  std::string client_common_set_hashes;
+  std::string client_cached_cert_hashes;
+};
+
+// QuicCryptoConfig contains common configuration between clients and servers.
+class NET_EXPORT_PRIVATE QuicCryptoConfig {
+ public:
+  // kInitialLabel is a constant that is used when deriving the initial
+  // (non-forward secure) keys for the connection in order to tie the resulting
+  // key to this protocol.
+  static const char kInitialLabel[];
+
+  // kCETVLabel is a constant that is used when deriving the keys for the
+  // encrypted tag/value block in the client hello.
+  static const char kCETVLabel[];
+
+  // kForwardSecureLabel is a constant that is used when deriving the forward
+  // secure keys for the connection in order to tie the resulting key to this
+  // protocol.
+  static const char kForwardSecureLabel[];
+
+  QuicCryptoConfig();
+  ~QuicCryptoConfig();
+
+  // Key exchange methods. The following two members' values correspond by
+  // index.
+  QuicTagVector kexs;
+  // Authenticated encryption with associated data (AEAD) algorithms.
+  QuicTagVector aead;
+
+  const CommonCertSets* common_cert_sets;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoConfig);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
diff --git a/net/quic/crypto/crypto_handshake_message.cc b/net/quic/crypto/crypto_handshake_message.cc
new file mode 100644
index 0000000..9125968
--- /dev/null
+++ b/net/quic/crypto/crypto_handshake_message.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_handshake_message.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_socket_address_coder.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace net {
+
+CryptoHandshakeMessage::CryptoHandshakeMessage()
+    : tag_(0),
+      minimum_size_(0) {}
+
+CryptoHandshakeMessage::CryptoHandshakeMessage(
+    const CryptoHandshakeMessage& other)
+    : tag_(other.tag_),
+      tag_value_map_(other.tag_value_map_),
+      minimum_size_(other.minimum_size_) {
+  // Don't copy serialized_. scoped_ptr doesn't have a copy constructor.
+  // The new object can lazily reconstruct serialized_.
+}
+
+CryptoHandshakeMessage::~CryptoHandshakeMessage() {}
+
+CryptoHandshakeMessage& CryptoHandshakeMessage::operator=(
+    const CryptoHandshakeMessage& other) {
+  tag_ = other.tag_;
+  tag_value_map_ = other.tag_value_map_;
+  // Don't copy serialized_. scoped_ptr doesn't have an assignment operator.
+  // However, invalidate serialized_.
+  serialized_.reset();
+  minimum_size_ = other.minimum_size_;
+  return *this;
+}
+
+void CryptoHandshakeMessage::Clear() {
+  tag_ = 0;
+  tag_value_map_.clear();
+  minimum_size_ = 0;
+  serialized_.reset();
+}
+
+const QuicData& CryptoHandshakeMessage::GetSerialized() const {
+  if (!serialized_.get()) {
+    serialized_.reset(CryptoFramer::ConstructHandshakeMessage(*this));
+  }
+  return *serialized_.get();
+}
+
+void CryptoHandshakeMessage::MarkDirty() {
+  serialized_.reset();
+}
+
+void CryptoHandshakeMessage::SetTaglist(QuicTag tag, ...) {
+  // Warning, if sizeof(QuicTag) > sizeof(int) then this function will break
+  // because the terminating 0 will only be promoted to int.
+  COMPILE_ASSERT(sizeof(QuicTag) <= sizeof(int),
+                 crypto_tag_may_not_be_larger_than_int_or_varargs_will_break);
+
+  vector<QuicTag> tags;
+  va_list ap;
+
+  va_start(ap, tag);
+  for (;;) {
+    QuicTag list_item = va_arg(ap, QuicTag);
+    if (list_item == 0) {
+      break;
+    }
+    tags.push_back(list_item);
+  }
+
+  // Because of the way that we keep tags in memory, we can copy the contents
+  // of the vector and get the correct bytes in wire format. See
+  // crypto_protocol.h. This assumes that the system is little-endian.
+  SetVector(tag, tags);
+
+  va_end(ap);
+}
+
+void CryptoHandshakeMessage::SetStringPiece(QuicTag tag, StringPiece value) {
+  tag_value_map_[tag] = value.as_string();
+}
+
+void CryptoHandshakeMessage::Erase(QuicTag tag) {
+  tag_value_map_.erase(tag);
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetTaglist(QuicTag tag,
+                                                 const QuicTag** out_tags,
+                                                 size_t* out_len) const {
+  QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+  QuicErrorCode ret = QUIC_NO_ERROR;
+
+  if (it == tag_value_map_.end()) {
+    ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+  } else if (it->second.size() % sizeof(QuicTag) != 0) {
+    ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  if (ret != QUIC_NO_ERROR) {
+    *out_tags = nullptr;
+    *out_len = 0;
+    return ret;
+  }
+
+  *out_tags = reinterpret_cast<const QuicTag*>(it->second.data());
+  *out_len = it->second.size() / sizeof(QuicTag);
+  return ret;
+}
+
+bool CryptoHandshakeMessage::GetStringPiece(QuicTag tag,
+                                            StringPiece* out) const {
+  QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+  if (it == tag_value_map_.end()) {
+    return false;
+  }
+  *out = it->second;
+  return true;
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetNthValue24(QuicTag tag,
+                                                    unsigned index,
+                                                    StringPiece* out) const {
+  StringPiece value;
+  if (!GetStringPiece(tag, &value)) {
+    return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+  }
+
+  for (unsigned i = 0;; i++) {
+    if (value.empty()) {
+      return QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND;
+    }
+    if (value.size() < 3) {
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    const unsigned char* data =
+        reinterpret_cast<const unsigned char*>(value.data());
+    size_t size = static_cast<size_t>(data[0]) |
+                  (static_cast<size_t>(data[1]) << 8) |
+                  (static_cast<size_t>(data[2]) << 16);
+    value.remove_prefix(3);
+
+    if (value.size() < size) {
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    if (i == index) {
+      *out = StringPiece(value.data(), size);
+      return QUIC_NO_ERROR;
+    }
+
+    value.remove_prefix(size);
+  }
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint16(QuicTag tag,
+                                                uint16* out) const {
+  return GetPOD(tag, out, sizeof(uint16));
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint32(QuicTag tag,
+                                                uint32* out) const {
+  return GetPOD(tag, out, sizeof(uint32));
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint64(QuicTag tag,
+                                                uint64* out) const {
+  return GetPOD(tag, out, sizeof(uint64));
+}
+
+size_t CryptoHandshakeMessage::size() const {
+  size_t ret = sizeof(QuicTag) +
+               sizeof(uint16) /* number of entries */ +
+               sizeof(uint16) /* padding */;
+  ret += (sizeof(QuicTag) + sizeof(uint32) /* end offset */) *
+         tag_value_map_.size();
+  for (QuicTagValueMap::const_iterator i = tag_value_map_.begin();
+       i != tag_value_map_.end(); ++i) {
+    ret += i->second.size();
+  }
+
+  return ret;
+}
+
+void CryptoHandshakeMessage::set_minimum_size(size_t min_bytes) {
+  if (min_bytes == minimum_size_) {
+    return;
+  }
+  serialized_.reset();
+  minimum_size_ = min_bytes;
+}
+
+size_t CryptoHandshakeMessage::minimum_size() const {
+  return minimum_size_;
+}
+
+string CryptoHandshakeMessage::DebugString() const {
+  return DebugStringInternal(0);
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetPOD(
+    QuicTag tag, void* out, size_t len) const {
+  QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+  QuicErrorCode ret = QUIC_NO_ERROR;
+
+  if (it == tag_value_map_.end()) {
+    ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+  } else if (it->second.size() != len) {
+    ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  if (ret != QUIC_NO_ERROR) {
+    memset(out, 0, len);
+    return ret;
+  }
+
+  memcpy(out, it->second.data(), len);
+  return ret;
+}
+
+string CryptoHandshakeMessage::DebugStringInternal(size_t indent) const {
+  string ret = string(2 * indent, ' ') + QuicUtils::TagToString(tag_) + "<\n";
+  ++indent;
+  for (QuicTagValueMap::const_iterator it = tag_value_map_.begin();
+       it != tag_value_map_.end(); ++it) {
+    ret += string(2 * indent, ' ') + QuicUtils::TagToString(it->first) + ": ";
+
+    bool done = false;
+    switch (it->first) {
+      case kICSL:
+      case kIFCW:
+      case kCFCW:
+      case kSFCW:
+      case kIRTT:
+      case kKATO:
+      case kMSPC:
+      case kSWND:
+        // uint32 value
+        if (it->second.size() == 4) {
+          uint32 value;
+          memcpy(&value, it->second.data(), sizeof(value));
+          ret += base::UintToString(value);
+          done = true;
+        }
+        break;
+      case kKEXS:
+      case kAEAD:
+      case kCGST:
+      case kCOPT:
+      case kPDMD:
+      case kVER:
+        // tag lists
+        if (it->second.size() % sizeof(QuicTag) == 0) {
+          for (size_t j = 0; j < it->second.size(); j += sizeof(QuicTag)) {
+            QuicTag tag;
+            memcpy(&tag, it->second.data() + j, sizeof(tag));
+            if (j > 0) {
+              ret += ",";
+            }
+            ret += "'" + QuicUtils::TagToString(tag) + "'";
+          }
+          done = true;
+        }
+        break;
+      case kCADR:
+        // IP address and port
+        if (!it->second.empty()) {
+          QuicSocketAddressCoder decoder;
+          if (decoder.Decode(it->second.data(), it->second.size())) {
+            ret += IPAddressToStringWithPort(decoder.ip(), decoder.port());
+            done = true;
+          }
+        }
+        break;
+      case kSCFG:
+        // nested messages.
+        if (!it->second.empty()) {
+          scoped_ptr<CryptoHandshakeMessage> msg(
+              CryptoFramer::ParseMessage(it->second));
+          if (msg.get()) {
+            ret += "\n";
+            ret += msg->DebugStringInternal(indent + 1);
+
+            done = true;
+          }
+        }
+        break;
+      case kPAD:
+        ret += StringPrintf("(%d bytes of padding)",
+                            static_cast<int>(it->second.size()));
+        done = true;
+        break;
+      case kUAID:
+        ret += it->second;
+        done = true;
+        break;
+    }
+
+    if (!done) {
+      // If there's no specific format for this tag, or the value is invalid,
+      // then just use hex.
+      ret += "0x" + base::HexEncode(it->second.data(), it->second.size());
+    }
+    ret += "\n";
+  }
+  --indent;
+  ret += string(2 * indent, ' ') + ">";
+  return ret;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_handshake_message.h b/net/quic/crypto/crypto_handshake_message.h
new file mode 100644
index 0000000..fcb3930
--- /dev/null
+++ b/net/quic/crypto/crypto_handshake_message.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
+#define NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// An intermediate format of a handshake message that's convenient for a
+// CryptoFramer to serialize from or parse into.
+class NET_EXPORT_PRIVATE CryptoHandshakeMessage {
+ public:
+  CryptoHandshakeMessage();
+  CryptoHandshakeMessage(const CryptoHandshakeMessage& other);
+  ~CryptoHandshakeMessage();
+
+  CryptoHandshakeMessage& operator=(const CryptoHandshakeMessage& other);
+
+  // Clears state.
+  void Clear();
+
+  // GetSerialized returns the serialized form of this message and caches the
+  // result. Subsequently altering the message does not invalidate the cache.
+  const QuicData& GetSerialized() const;
+
+  // MarkDirty invalidates the cache created by |GetSerialized|.
+  void MarkDirty();
+
+  // SetValue sets an element with the given tag to the raw, memory contents of
+  // |v|.
+  template<class T> void SetValue(QuicTag tag, const T& v) {
+    tag_value_map_[tag] =
+        std::string(reinterpret_cast<const char*>(&v), sizeof(v));
+  }
+
+  // SetVector sets an element with the given tag to the raw contents of an
+  // array of elements in |v|.
+  template<class T> void SetVector(QuicTag tag, const std::vector<T>& v) {
+    if (v.empty()) {
+      tag_value_map_[tag] = std::string();
+    } else {
+      tag_value_map_[tag] = std::string(reinterpret_cast<const char*>(&v[0]),
+                                        v.size() * sizeof(T));
+    }
+  }
+
+  // Returns the message tag.
+  QuicTag tag() const { return tag_; }
+  // Sets the message tag.
+  void set_tag(QuicTag tag) { tag_ = tag; }
+
+  const QuicTagValueMap& tag_value_map() const { return tag_value_map_; }
+
+  // SetTaglist sets an element with the given tag to contain a list of tags,
+  // passed as varargs. The argument list must be terminated with a 0 element.
+  void SetTaglist(QuicTag tag, ...);
+
+  void SetStringPiece(QuicTag tag, base::StringPiece value);
+
+  // Erase removes a tag/value, if present, from the message.
+  void Erase(QuicTag tag);
+
+  // GetTaglist finds an element with the given tag containing zero or more
+  // tags. If such a tag doesn't exist, it returns false. Otherwise it sets
+  // |out_tags| and |out_len| to point to the array of tags and returns true.
+  // The array points into the CryptoHandshakeMessage and is valid only for as
+  // long as the CryptoHandshakeMessage exists and is not modified.
+  QuicErrorCode GetTaglist(QuicTag tag, const QuicTag** out_tags,
+                           size_t* out_len) const;
+
+  bool GetStringPiece(QuicTag tag, base::StringPiece* out) const;
+
+  // GetNthValue24 interprets the value with the given tag to be a series of
+  // 24-bit, length prefixed values and it returns the subvalue with the given
+  // index.
+  QuicErrorCode GetNthValue24(QuicTag tag,
+                              unsigned index,
+                              base::StringPiece* out) const;
+  QuicErrorCode GetUint16(QuicTag tag, uint16* out) const;
+  QuicErrorCode GetUint32(QuicTag tag, uint32* out) const;
+  QuicErrorCode GetUint64(QuicTag tag, uint64* out) const;
+
+  // size returns 4 (message tag) + 2 (uint16, number of entries) +
+  // (4 (tag) + 4 (end offset))*tag_value_map_.size() + ∑ value sizes.
+  size_t size() const;
+
+  // set_minimum_size sets the minimum number of bytes that the message should
+  // consume. The CryptoFramer will add a PAD tag as needed when serializing in
+  // order to ensure this. Setting a value of 0 disables padding.
+  //
+  // Padding is useful in order to ensure that messages are a minimum size. A
+  // QUIC server can require a minimum size in order to reduce the
+  // amplification factor of any mirror DoS attack.
+  void set_minimum_size(size_t min_bytes);
+
+  size_t minimum_size() const;
+
+  // DebugString returns a multi-line, string representation of the message
+  // suitable for including in debug output.
+  std::string DebugString() const;
+
+ private:
+  // GetPOD is a utility function for extracting a plain-old-data value. If
+  // |tag| exists in the message, and has a value of exactly |len| bytes then
+  // it copies |len| bytes of data into |out|. Otherwise |len| bytes at |out|
+  // are zeroed out.
+  //
+  // If used to copy integers then this assumes that the machine is
+  // little-endian.
+  QuicErrorCode GetPOD(QuicTag tag, void* out, size_t len) const;
+
+  std::string DebugStringInternal(size_t indent) const;
+
+  QuicTag tag_;
+  QuicTagValueMap tag_value_map_;
+
+  size_t minimum_size_;
+
+  // The serialized form of the handshake message. This member is constructed
+  // lasily.
+  mutable scoped_ptr<QuicData> serialized_;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_MESSAGE_H_
diff --git a/net/quic/crypto/crypto_protocol.h b/net/quic/crypto/crypto_protocol.h
new file mode 100644
index 0000000..0dd3b34
--- /dev/null
+++ b/net/quic/crypto/crypto_protocol.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
+#define NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+// Version and Crypto tags are written to the wire with a big-endian
+// representation of the name of the tag.  For example
+// the client hello tag (CHLO) will be written as the
+// following 4 bytes: 'C' 'H' 'L' 'O'.  Since it is
+// stored in memory as a little endian uint32, we need
+// to reverse the order of the bytes.
+//
+// We use a macro to ensure that no static initialisers are created. Use the
+// MakeQuicTag function in normal code.
+#define TAG(a, b, c, d) \
+    static_cast<QuicTag>((d << 24) + (c << 16) + (b << 8) + a)
+
+namespace net {
+
+typedef std::string ServerConfigID;
+
+const QuicTag kCHLO = TAG('C', 'H', 'L', 'O');   // Client hello
+const QuicTag kSHLO = TAG('S', 'H', 'L', 'O');   // Server hello
+const QuicTag kSCFG = TAG('S', 'C', 'F', 'G');   // Server config
+const QuicTag kREJ  = TAG('R', 'E', 'J', '\0');  // Reject
+const QuicTag kCETV = TAG('C', 'E', 'T', 'V');   // Client encrypted tag-value
+                                                 // pairs
+const QuicTag kPRST = TAG('P', 'R', 'S', 'T');   // Public reset
+const QuicTag kSCUP = TAG('S', 'C', 'U', 'P');   // Server config update.
+
+// Key exchange methods
+const QuicTag kP256 = TAG('P', '2', '5', '6');   // ECDH, Curve P-256
+const QuicTag kC255 = TAG('C', '2', '5', '5');   // ECDH, Curve25519
+
+// AEAD algorithms
+const QuicTag kNULL = TAG('N', 'U', 'L', 'N');   // null algorithm
+const QuicTag kAESG = TAG('A', 'E', 'S', 'G');   // AES128 + GCM-12
+const QuicTag kCC12 = TAG('C', 'C', '1', '2');   // ChaCha20 + Poly1305
+
+// Socket receive buffer
+const QuicTag kSRBF = TAG('S', 'R', 'B', 'F');   // Socket receive buffer
+
+// Congestion control feedback types
+const QuicTag kQBIC = TAG('Q', 'B', 'I', 'C');   // TCP cubic
+
+// Connection options (COPT) values
+const QuicTag kTBBR = TAG('T', 'B', 'B', 'R');   // Reduced Buffer Bloat TCP
+const QuicTag kRENO = TAG('R', 'E', 'N', 'O');   // Reno Congestion Control
+const QuicTag kIW10 = TAG('I', 'W', '1', '0');   // Force ICWND to 10
+const QuicTag kPACE = TAG('P', 'A', 'C', 'E');   // Paced TCP cubic
+const QuicTag k1CON = TAG('1', 'C', 'O', 'N');   // Emulate a single connection
+
+// Loss detection algorithm types
+const QuicTag kNACK = TAG('N', 'A', 'C', 'K');   // TCP style nack counting
+const QuicTag kTIME = TAG('T', 'I', 'M', 'E');   // Time based
+
+// FEC options
+const QuicTag kFHDR = TAG('F', 'H', 'D', 'R');   // FEC protect headers
+
+// Proof types (i.e. certificate types)
+// NOTE: although it would be silly to do so, specifying both kX509 and kX59R
+// is allowed and is equivalent to specifying only kX509.
+const QuicTag kX509 = TAG('X', '5', '0', '9');   // X.509 certificate, all key
+                                                 // types
+const QuicTag kX59R = TAG('X', '5', '9', 'R');   // X.509 certificate, RSA keys
+                                                 // only
+const QuicTag kCHID = TAG('C', 'H', 'I', 'D');   // Channel ID.
+
+// Client hello tags
+const QuicTag kVER  = TAG('V', 'E', 'R', '\0');  // Version (new)
+const QuicTag kNONC = TAG('N', 'O', 'N', 'C');   // The client's nonce
+const QuicTag kKEXS = TAG('K', 'E', 'X', 'S');   // Key exchange methods
+const QuicTag kAEAD = TAG('A', 'E', 'A', 'D');   // Authenticated
+                                                 // encryption algorithms
+const QuicTag kCGST = TAG('C', 'G', 'S', 'T');   // Congestion control
+                                                 // feedback types
+const QuicTag kCOPT = TAG('C', 'O', 'P', 'T');   // Connection options
+const QuicTag kICSL = TAG('I', 'C', 'S', 'L');   // Idle connection state
+                                                 // lifetime
+const QuicTag kKATO = TAG('K', 'A', 'T', 'O');   // Keepalive timeout
+const QuicTag kMSPC = TAG('M', 'S', 'P', 'C');   // Max streams per connection.
+const QuicTag kIRTT = TAG('I', 'R', 'T', 'T');   // Estimated initial RTT in us.
+const QuicTag kSWND = TAG('S', 'W', 'N', 'D');   // Server's Initial congestion
+                                                 // window.
+const QuicTag kSNI  = TAG('S', 'N', 'I', '\0');  // Server name
+                                                 // indication
+const QuicTag kPUBS = TAG('P', 'U', 'B', 'S');   // Public key values
+const QuicTag kSCID = TAG('S', 'C', 'I', 'D');   // Server config id
+const QuicTag kORBT = TAG('O', 'B', 'I', 'T');   // Server orbit.
+const QuicTag kPDMD = TAG('P', 'D', 'M', 'D');   // Proof demand.
+const QuicTag kPROF = TAG('P', 'R', 'O', 'F');   // Proof (signature).
+const QuicTag kCCS  = TAG('C', 'C', 'S', 0);     // Common certificate set
+const QuicTag kCCRT = TAG('C', 'C', 'R', 'T');   // Cached certificate
+const QuicTag kEXPY = TAG('E', 'X', 'P', 'Y');   // Expiry
+// TODO(rjshade): Remove kIFCW when removing QUIC_VERSION_19.
+const QuicTag kIFCW = TAG('I', 'F', 'C', 'W');   // Initial flow control receive
+                                                 // window.
+const QuicTag kSFCW = TAG('S', 'F', 'C', 'W');   // Initial stream flow control
+                                                 // receive window.
+const QuicTag kCFCW = TAG('C', 'F', 'C', 'W');   // Initial session/connection
+                                                 // flow control receive window.
+const QuicTag kUAID = TAG('U', 'A', 'I', 'D');   // Client's User Agent ID.
+
+// Rejection tags
+const QuicTag kRREJ = TAG('R', 'R', 'E', 'J');   // Reasons for server sending
+                                                 // rejection message tag.
+
+// Server hello tags
+const QuicTag kCADR = TAG('C', 'A', 'D', 'R');   // Client IP address and port
+
+// CETV tags
+const QuicTag kCIDK = TAG('C', 'I', 'D', 'K');   // ChannelID key
+const QuicTag kCIDS = TAG('C', 'I', 'D', 'S');   // ChannelID signature
+
+// Public reset tags
+const QuicTag kRNON = TAG('R', 'N', 'O', 'N');   // Public reset nonce proof
+const QuicTag kRSEQ = TAG('R', 'S', 'E', 'Q');   // Rejected sequence number
+
+// Universal tags
+const QuicTag kPAD  = TAG('P', 'A', 'D', '\0');  // Padding
+
+// These tags have a special form so that they appear either at the beginning
+// or the end of a handshake message. Since handshake messages are sorted by
+// tag value, the tags with 0 at the end will sort first and those with 255 at
+// the end will sort last.
+//
+// The certificate chain should have a tag that will cause it to be sorted at
+// the end of any handshake messages because it's likely to be large and the
+// client might be able to get everything that it needs from the small values at
+// the beginning.
+//
+// Likewise tags with random values should be towards the beginning of the
+// message because the server mightn't hold state for a rejected client hello
+// and therefore the client may have issues reassembling the rejection message
+// in the event that it sent two client hellos.
+const QuicTag kServerNonceTag =
+    TAG('S', 'N', 'O', 0);  // The server's nonce
+const QuicTag kSourceAddressTokenTag =
+    TAG('S', 'T', 'K', 0);  // Source-address token
+const QuicTag kCertificateTag =
+    TAG('C', 'R', 'T', 255);  // Certificate chain
+
+#undef TAG
+
+const size_t kMaxEntries = 128;  // Max number of entries in a message.
+
+const size_t kNonceSize = 32;  // Size in bytes of the connection nonce.
+
+const size_t kOrbitSize = 8;  // Number of bytes in an orbit value.
+
+// kProofSignatureLabel is prepended to server configs before signing to avoid
+// any cross-protocol attacks on the signature.
+const char kProofSignatureLabel[] = "QUIC server config signature";
+
+// kClientHelloMinimumSize is the minimum size of a client hello. Client hellos
+// will have PAD tags added in order to ensure this minimum is met and client
+// hellos smaller than this will be an error. This minimum size reduces the
+// amplification factor of any mirror DoS attack.
+//
+// A client may pad an inchoate client hello to a size larger than
+// kClientHelloMinimumSize to make it more likely to receive a complete
+// rejection message.
+const size_t kClientHelloMinimumSize = 1024;
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
diff --git a/net/quic/crypto/crypto_secret_boxer.cc b/net/quic/crypto/crypto_secret_boxer.cc
new file mode 100644
index 0000000..5d8a11b
--- /dev/null
+++ b/net/quic/crypto/crypto_secret_boxer.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_secret_boxer.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// Defined kKeySize for GetKeySize() and SetKey().
+static const size_t kKeySize = 16;
+
+// kBoxNonceSize contains the number of bytes of nonce that we use in each box.
+// TODO(rtenneti): Add support for kBoxNonceSize to be 16 bytes.
+//
+// From agl@:
+//   96-bit nonces are on the edge. An attacker who can collect 2^41
+//   source-address tokens has a 1% chance of finding a duplicate.
+//
+//   The "average" DDoS is now 32.4M PPS. That's 2^25 source-address tokens
+//   per second. So one day of that DDoS botnot would reach the 1% mark.
+//
+//   It's not terrible, but it's not a "forget about it" margin.
+static const size_t kBoxNonceSize = 12;
+
+// static
+size_t CryptoSecretBoxer::GetKeySize() { return kKeySize; }
+
+void CryptoSecretBoxer::SetKey(StringPiece key) {
+  DCHECK_EQ(kKeySize, key.size());
+  key_ = key.as_string();
+}
+
+string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const {
+  scoped_ptr<QuicEncrypter> encrypter(QuicEncrypter::Create(kAESG));
+  if (!encrypter->SetKey(key_)) {
+    DLOG(DFATAL) << "CryptoSecretBoxer's encrypter->SetKey failed.";
+    return string();
+  }
+  size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length());
+
+  string ret;
+  const size_t len = kBoxNonceSize + ciphertext_size;
+  ret.resize(len);
+  char* data = &ret[0];
+
+  // Generate nonce.
+  rand->RandBytes(data, kBoxNonceSize);
+  memcpy(data + kBoxNonceSize, plaintext.data(), plaintext.size());
+
+  if (!encrypter->Encrypt(StringPiece(data, kBoxNonceSize), StringPiece(),
+                          plaintext, reinterpret_cast<unsigned char*>(
+                                         data + kBoxNonceSize))) {
+    DLOG(DFATAL) << "CryptoSecretBoxer's Encrypt failed.";
+    return string();
+  }
+
+  return ret;
+}
+
+bool CryptoSecretBoxer::Unbox(StringPiece ciphertext,
+                              string* out_storage,
+                              StringPiece* out) const {
+  if (ciphertext.size() < kBoxNonceSize) {
+    return false;
+  }
+
+  char nonce[kBoxNonceSize];
+  memcpy(nonce, ciphertext.data(), kBoxNonceSize);
+  ciphertext.remove_prefix(kBoxNonceSize);
+
+  size_t len = ciphertext.size();
+  out_storage->resize(len);
+  char* data = const_cast<char*>(out_storage->data());
+
+  scoped_ptr<QuicDecrypter> decrypter(QuicDecrypter::Create(kAESG));
+  if (!decrypter->SetKey(key_)) {
+    DLOG(DFATAL) << "CryptoSecretBoxer's decrypter->SetKey failed.";
+    return false;
+  }
+  if (!decrypter->Decrypt(StringPiece(nonce, kBoxNonceSize), StringPiece(),
+                          ciphertext, reinterpret_cast<unsigned char*>(data),
+                          &len)) {
+    return false;
+  }
+
+  out->set(data, len);
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_secret_boxer.h b/net/quic/crypto/crypto_secret_boxer.h
new file mode 100644
index 0000000..38b8fb3
--- /dev/null
+++ b/net/quic/crypto/crypto_secret_boxer.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
+#define NET_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class QuicRandom;
+
+// CryptoSecretBoxer encrypts small chunks of plaintext (called 'boxing') and
+// then, later, can authenticate+decrypt the resulting boxes. This object is
+// thread-safe.
+class NET_EXPORT_PRIVATE CryptoSecretBoxer {
+ public:
+  CryptoSecretBoxer() {}
+
+  // GetKeySize returns the number of bytes in a key.
+  static size_t GetKeySize();
+
+  // SetKey sets the key for this object. This must be done before |Box| or
+  // |Unbox| are called. |key| must be |GetKeySize()| bytes long.
+  void SetKey(base::StringPiece key);
+
+  // Box encrypts |plaintext| using a random nonce generated from |rand| and
+  // returns the resulting ciphertext. Since an authenticator and nonce are
+  // included, the result will be slightly larger than |plaintext|.
+  std::string Box(QuicRandom* rand, base::StringPiece plaintext) const;
+
+  // Unbox takes the result of a previous call to |Box| in |ciphertext| and
+  // authenticates+decrypts it. If |ciphertext| is not authentic then it
+  // returns false. Otherwise, |out_storage| is used to store the result and
+  // |out| is set to point into |out_storage| and contains the original
+  // plaintext.
+  bool Unbox(base::StringPiece ciphertext,
+             std::string* out_storage,
+             base::StringPiece* out) const;
+
+ private:
+  std::string key_;
+
+  DISALLOW_COPY_AND_ASSIGN(CryptoSecretBoxer);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
diff --git a/net/quic/crypto/crypto_secret_boxer_test.cc b/net/quic/crypto/crypto_secret_boxer_test.cc
new file mode 100644
index 0000000..427d052
--- /dev/null
+++ b/net/quic/crypto/crypto_secret_boxer_test.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_secret_boxer.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/quic_random.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+TEST(CryptoSecretBoxerTest, BoxAndUnbox) {
+  StringPiece message("hello world");
+  const size_t key_size = CryptoSecretBoxer::GetKeySize();
+  scoped_ptr<uint8[]> key(new uint8[key_size]);
+  memset(key.get(), 0x11, key_size);
+
+  CryptoSecretBoxer boxer;
+  boxer.SetKey(StringPiece(reinterpret_cast<char*>(key.get()), key_size));
+
+  const string box = boxer.Box(QuicRandom::GetInstance(), message);
+
+  string storage;
+  StringPiece result;
+  EXPECT_TRUE(boxer.Unbox(box, &storage, &result));
+  EXPECT_EQ(result, message);
+
+  EXPECT_FALSE(boxer.Unbox(string(1, 'X') + box, &storage, &result));
+  EXPECT_FALSE(boxer.Unbox(box.substr(1, string::npos), &storage, &result));
+  EXPECT_FALSE(boxer.Unbox(string(), &storage, &result));
+  EXPECT_FALSE(boxer.Unbox(string(1, box[0]^0x80) + box.substr(1, string::npos),
+                           &storage, &result));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/crypto_server_config_protobuf.cc b/net/quic/crypto/crypto_server_config_protobuf.cc
new file mode 100644
index 0000000..d292f9e
--- /dev/null
+++ b/net/quic/crypto/crypto_server_config_protobuf.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_server_config_protobuf.h"
+
+#include "base/stl_util.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+QuicServerConfigProtobuf::QuicServerConfigProtobuf()
+    : primary_time_(QuicWallTime::Zero().ToUNIXSeconds()),
+      priority_(0) {
+}
+
+QuicServerConfigProtobuf::~QuicServerConfigProtobuf() {
+  STLDeleteElements(&keys_);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_server_config_protobuf.h b/net/quic/crypto/crypto_server_config_protobuf.h
new file mode 100644
index 0000000..57ebfb0
--- /dev/null
+++ b/net/quic/crypto/crypto_server_config_protobuf.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
+#define NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// QuicServerConfigProtobuf contains QUIC server config block and the private
+// keys needed to prove ownership.
+// TODO(rch): sync with server more rationally.
+class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
+ public:
+  // PrivateKey contains a QUIC tag of a key exchange algorithm and a
+  // serialised private key for that algorithm. The format of the serialised
+  // private key is specific to the algorithm in question.
+  class NET_EXPORT_PRIVATE PrivateKey {
+   public:
+    QuicTag tag() const {
+      return tag_;
+    }
+    void set_tag(QuicTag tag) {
+      tag_ = tag;
+    }
+    std::string private_key() const {
+      return private_key_;
+    }
+    void set_private_key(std::string key) {
+      private_key_ = key;
+    }
+
+   private:
+    QuicTag tag_;
+    std::string private_key_;
+  };
+
+  QuicServerConfigProtobuf();
+  ~QuicServerConfigProtobuf();
+
+  size_t key_size() const {
+    return keys_.size();
+  }
+
+  const PrivateKey& key(size_t i) const {
+    DCHECK_GT(keys_.size(), i);
+    return *keys_[i];
+  }
+
+  std::string config() const {
+    return config_;
+  }
+
+  void set_config(base::StringPiece config) {
+    config.CopyToString(&config_);
+  }
+
+  QuicServerConfigProtobuf::PrivateKey* add_key() {
+    keys_.push_back(new PrivateKey);
+    return keys_.back();
+  }
+
+  void clear_key() {
+    STLDeleteElements(&keys_);
+  }
+
+  bool has_primary_time() const {
+    return primary_time_ > 0;
+  }
+
+  int64 primary_time() const {
+    return primary_time_;
+  }
+
+  void set_primary_time(int64 primary_time) {
+    primary_time_ = primary_time;
+  }
+
+  bool has_priority() const {
+    return priority_ > 0;
+  }
+
+  uint64 priority() const {
+    return priority_;
+  }
+
+  void set_priority(int64 priority) {
+    priority_ = priority;
+  }
+
+  bool has_source_address_token_secret_override() const {
+    return !source_address_token_secret_override_.empty();
+  }
+
+  std::string source_address_token_secret_override() const {
+    return source_address_token_secret_override_;
+  }
+
+  void set_source_address_token_secret_override(
+      base::StringPiece source_address_token_secret_override) {
+    source_address_token_secret_override.CopyToString(
+        &source_address_token_secret_override_);
+  }
+
+ private:
+  std::vector<PrivateKey*> keys_;
+
+  // config_ is a serialised config in QUIC wire format.
+  std::string config_;
+
+  // primary_time_ contains a UNIX epoch seconds value that indicates when this
+  // config should become primary.
+  int64 primary_time_;
+
+  // Relative priority of this config vs other configs with the same
+  // primary time.  For use as a secondary sort key when selecting the
+  // primary config.
+  uint64 priority_;
+
+  // Optional override to the secret used to box/unbox source address
+  // tokens when talking to clients that select this server config.
+  // It can be of any length as it is fed into a KDF before use.
+  std::string source_address_token_secret_override_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicServerConfigProtobuf);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
diff --git a/net/quic/crypto/crypto_server_test.cc b/net/quic/crypto/crypto_server_test.cc
new file mode 100644
index 0000000..bb47cbf
--- /dev/null
+++ b/net/quic/crypto/crypto_server_test.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ostream>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_number_conversions.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_socket_address_coder.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/delayed_verify_strike_register_client.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::ostream;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class QuicCryptoServerConfigPeer {
+ public:
+  explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config)
+      : server_config_(server_config) {}
+
+  base::Lock* GetStrikeRegisterClientLock() {
+    return &server_config_->strike_register_client_lock_;
+  }
+
+ private:
+  QuicCryptoServerConfig* server_config_;
+};
+
+// Run tests with combinations of
+// {FLAGS_use_early_return_when_verifying_chlo,
+//  FLAGS_send_quic_crypto_reject_reason}.
+struct TestParams {
+  TestParams(bool use_early_return_when_verifying_chlo,
+             bool send_quic_crypto_reject_reason)
+      : use_early_return_when_verifying_chlo(
+            use_early_return_when_verifying_chlo),
+        send_quic_crypto_reject_reason(send_quic_crypto_reject_reason) {
+  }
+
+  friend ostream& operator<<(ostream& os, const TestParams& p) {
+    os << "{ use_early_return_when_verifying_chlo: "
+       << p.use_early_return_when_verifying_chlo
+       << " send_quic_crypto_reject_reason: "
+       << p.send_quic_crypto_reject_reason << " }";
+    return os;
+  }
+
+  bool use_early_return_when_verifying_chlo;
+  bool send_quic_crypto_reject_reason;
+};
+
+// Constructs various test permutations.
+vector<TestParams> GetTestParams() {
+  vector<TestParams> params;
+  params.push_back(TestParams(false, false));
+  params.push_back(TestParams(false, true));
+  params.push_back(TestParams(true, false));
+  params.push_back(TestParams(true, true));
+  return params;
+}
+
+class CryptoServerTest : public ::testing::TestWithParam<TestParams> {
+ public:
+  CryptoServerTest()
+      : rand_(QuicRandom::GetInstance()),
+        client_address_(Loopback4(), 1234),
+        config_(QuicCryptoServerConfig::TESTING, rand_) {
+    config_.SetProofSource(CryptoTestUtils::ProofSourceForTesting());
+    supported_versions_ = QuicSupportedVersions();
+    client_version_ = QuicUtils::TagToString(
+        QuicVersionToQuicTag(supported_versions_.front()));
+
+    FLAGS_use_early_return_when_verifying_chlo =
+        GetParam().use_early_return_when_verifying_chlo;
+    FLAGS_send_quic_crypto_reject_reason =
+        GetParam().send_quic_crypto_reject_reason;
+  }
+
+  virtual void SetUp() {
+    scoped_ptr<CryptoHandshakeMessage> msg(
+        config_.AddDefaultConfig(rand_, &clock_,
+        config_options_));
+
+    StringPiece orbit;
+    CHECK(msg->GetStringPiece(kORBT, &orbit));
+    CHECK_EQ(sizeof(orbit_), orbit.size());
+    memcpy(orbit_, orbit.data(), orbit.size());
+
+    char public_value[32];
+    memset(public_value, 42, sizeof(public_value));
+
+    const string nonce_str = GenerateNonce();
+    nonce_hex_ = "#" + base::HexEncode(nonce_str.data(), nonce_str.size());
+    pub_hex_ = "#" + base::HexEncode(public_value, sizeof(public_value));
+
+    CryptoHandshakeMessage client_hello = CryptoTestUtils::Message(
+        "CHLO",
+        "AEAD", "AESG",
+        "KEXS", "C255",
+        "PUBS", pub_hex_.c_str(),
+        "NONC", nonce_hex_.c_str(),
+        "VER\0", client_version_.data(),
+        "$padding", static_cast<int>(kClientHelloMinimumSize),
+        nullptr);
+    ShouldSucceed(client_hello);
+    // The message should be rejected because the source-address token is
+    // missing.
+    ASSERT_EQ(kREJ, out_.tag());
+    const HandshakeFailureReason kRejectReasons[] = {
+      SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+    };
+    CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+
+    StringPiece srct;
+    ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct));
+    srct_hex_ = "#" + base::HexEncode(srct.data(), srct.size());
+
+    StringPiece scfg;
+    ASSERT_TRUE(out_.GetStringPiece(kSCFG, &scfg));
+    server_config_.reset(CryptoFramer::ParseMessage(scfg));
+
+    StringPiece scid;
+    ASSERT_TRUE(server_config_->GetStringPiece(kSCID, &scid));
+    scid_hex_ = "#" + base::HexEncode(scid.data(), scid.size());
+  }
+
+  // Helper used to accept the result of ValidateClientHello and pass
+  // it on to ProcessClientHello.
+  class ValidateCallback : public ValidateClientHelloResultCallback {
+   public:
+    ValidateCallback(CryptoServerTest* test,
+                     bool should_succeed,
+                     const char* error_substr,
+                     bool* called)
+        : test_(test),
+          should_succeed_(should_succeed),
+          error_substr_(error_substr),
+          called_(called) {
+      *called_ = false;
+    }
+
+    virtual void RunImpl(const CryptoHandshakeMessage& client_hello,
+                         const Result& result) OVERRIDE {
+      {
+        // Ensure that the strike register client lock is not held.
+        QuicCryptoServerConfigPeer peer(&test_->config_);
+        base::Lock* m = peer.GetStrikeRegisterClientLock();
+        // In Chromium, we will dead lock if the lock is held by the current
+        // thread. Chromium doesn't have AssertNotHeld API call.
+        // m->AssertNotHeld();
+        base::AutoLock lock(*m);
+      }
+      ASSERT_FALSE(*called_);
+      test_->ProcessValidationResult(
+          client_hello, result, should_succeed_, error_substr_);
+      *called_ = true;
+    }
+
+   private:
+    CryptoServerTest* test_;
+    bool should_succeed_;
+    const char* error_substr_;
+    bool* called_;
+  };
+
+  void CheckServerHello(const CryptoHandshakeMessage& server_hello) {
+    const QuicTag* versions;
+    size_t num_versions;
+    server_hello.GetTaglist(kVER, &versions, &num_versions);
+    ASSERT_EQ(QuicSupportedVersions().size(), num_versions);
+    for (size_t i = 0; i < num_versions; ++i) {
+      EXPECT_EQ(QuicVersionToQuicTag(QuicSupportedVersions()[i]), versions[i]);
+    }
+
+    StringPiece address;
+    ASSERT_TRUE(server_hello.GetStringPiece(kCADR, &address));
+    QuicSocketAddressCoder decoder;
+    ASSERT_TRUE(decoder.Decode(address.data(), address.size()));
+    EXPECT_EQ(client_address_.address(), decoder.ip());
+    EXPECT_EQ(client_address_.port(), decoder.port());
+  }
+
+  void ShouldSucceed(const CryptoHandshakeMessage& message) {
+    bool called = false;
+    RunValidate(message, new ValidateCallback(this, true, "", &called));
+    EXPECT_TRUE(called);
+  }
+
+  void RunValidate(
+      const CryptoHandshakeMessage& message,
+      ValidateClientHelloResultCallback* cb) {
+    config_.ValidateClientHello(message, client_address_, &clock_, cb);
+  }
+
+  void ShouldFailMentioning(const char* error_substr,
+                            const CryptoHandshakeMessage& message) {
+    bool called = false;
+    ShouldFailMentioning(error_substr, message, &called);
+    EXPECT_TRUE(called);
+  }
+
+  void ShouldFailMentioning(const char* error_substr,
+                            const CryptoHandshakeMessage& message,
+                            bool* called) {
+    config_.ValidateClientHello(
+        message, client_address_, &clock_,
+        new ValidateCallback(this, false, error_substr, called));
+  }
+
+  void ProcessValidationResult(const CryptoHandshakeMessage& message,
+                               const ValidateCallback::Result& result,
+                               bool should_succeed,
+                               const char* error_substr) {
+    string error_details;
+    QuicErrorCode error = config_.ProcessClientHello(
+        result, 1 /* ConnectionId */, client_address_,
+        supported_versions_.front(), supported_versions_, &clock_, rand_,
+        &params_, &out_, &error_details);
+
+    if (should_succeed) {
+      ASSERT_EQ(error, QUIC_NO_ERROR)
+          << "Message failed with error " << error_details << ": "
+          << message.DebugString();
+    } else {
+      ASSERT_NE(error, QUIC_NO_ERROR)
+          << "Message didn't fail: " << message.DebugString();
+
+      EXPECT_TRUE(error_details.find(error_substr) != string::npos)
+          << error_substr << " not in " << error_details;
+    }
+  }
+
+  CryptoHandshakeMessage InchoateClientHello(const char* message_tag, ...) {
+    va_list ap;
+    va_start(ap, message_tag);
+
+    CryptoHandshakeMessage message =
+        CryptoTestUtils::BuildMessage(message_tag, ap);
+    va_end(ap);
+
+    message.SetStringPiece(kPAD, string(kClientHelloMinimumSize, '-'));
+    return message;
+  }
+
+  string GenerateNonce() {
+    string nonce;
+    CryptoUtils::GenerateNonce(
+        clock_.WallNow(), rand_,
+        StringPiece(reinterpret_cast<const char*>(orbit_), sizeof(orbit_)),
+        &nonce);
+    return nonce;
+  }
+
+  void CheckRejectReasons(
+      const HandshakeFailureReason* expected_handshake_failures,
+      size_t expected_count) {
+    const uint32* reject_reasons;
+    size_t num_reject_reasons;
+    COMPILE_ASSERT(sizeof(QuicTag) == sizeof(uint32), header_out_of_sync);
+    QuicErrorCode error_code = out_.GetTaglist(kRREJ, &reject_reasons,
+                                               &num_reject_reasons);
+    if (!FLAGS_send_quic_crypto_reject_reason) {
+      ASSERT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error_code);
+      return;
+    }
+    ASSERT_EQ(QUIC_NO_ERROR, error_code);
+
+    if (FLAGS_use_early_return_when_verifying_chlo) {
+      EXPECT_EQ(1u, num_reject_reasons);
+    } else {
+      EXPECT_EQ(expected_count, num_reject_reasons);
+    }
+    for (size_t i = 0; i < num_reject_reasons; ++i) {
+      EXPECT_EQ(expected_handshake_failures[i], reject_reasons[i]);
+    }
+  }
+
+ protected:
+  QuicRandom* const rand_;
+  MockClock clock_;
+  const IPEndPoint client_address_;
+  QuicVersionVector supported_versions_;
+  string client_version_;
+  QuicCryptoServerConfig config_;
+  QuicCryptoServerConfig::ConfigOptions config_options_;
+  QuicCryptoNegotiatedParameters params_;
+  CryptoHandshakeMessage out_;
+  uint8 orbit_[kOrbitSize];
+
+  // These strings contain hex escaped values from the server suitable for
+  // passing to |InchoateClientHello| when constructing client hello messages.
+  string nonce_hex_, pub_hex_, srct_hex_, scid_hex_;
+  scoped_ptr<CryptoHandshakeMessage> server_config_;
+};
+
+// Run all CryptoServerTest with all combinations of
+// FLAGS_use_early_return_when_verifying_chlo and
+// FLAGS_send_quic_crypto_reject_reason.
+INSTANTIATE_TEST_CASE_P(CryptoServerTests,
+                        CryptoServerTest,
+                        ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(CryptoServerTest, BadSNI) {
+  static const char* kBadSNIs[] = {
+    "",
+    "foo",
+    "#00",
+    "#ff00",
+    "127.0.0.1",
+    "ffee::1",
+  };
+
+  string client_version = QuicUtils::TagToString(
+      QuicVersionToQuicTag(supported_versions_.front()));
+
+  for (size_t i = 0; i < arraysize(kBadSNIs); i++) {
+    ShouldFailMentioning("SNI", InchoateClientHello(
+        "CHLO",
+        "SNI", kBadSNIs[i],
+        "VER\0", client_version.data(),
+        nullptr));
+    const HandshakeFailureReason kRejectReasons[] = {
+      SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+    };
+    CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+  }
+}
+
+// TODO(rtenneti): Enable the DefaultCert test after implementing ProofSource.
+TEST_F(CryptoServerTest, DISABLED_DefaultCert) {
+  // Check that the server replies with a default certificate when no SNI is
+  // specified.
+  ShouldSucceed(InchoateClientHello(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", nonce_hex_.c_str(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      "PDMD", "X509",
+      "VER\0", client_version_.data(),
+      nullptr));
+
+  StringPiece cert, proof;
+  EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
+  EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
+  EXPECT_NE(0u, cert.size());
+  EXPECT_NE(0u, proof.size());
+  const HandshakeFailureReason kRejectReasons[] = {
+    CLIENT_NONCE_INVALID_TIME_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, TooSmall) {
+  ShouldFailMentioning("too small", CryptoTestUtils::Message(
+        "CHLO",
+        "VER\0", client_version_.data(),
+        nullptr));
+  const HandshakeFailureReason kRejectReasons[] = {
+    SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, BadSourceAddressToken) {
+  // Invalid source-address tokens should be ignored.
+  static const char* kBadSourceAddressTokens[] = {
+    "",
+    "foo",
+    "#0000",
+    "#0000000000000000000000000000000000000000",
+  };
+
+  for (size_t i = 0; i < arraysize(kBadSourceAddressTokens); i++) {
+    ShouldSucceed(InchoateClientHello(
+        "CHLO",
+        "STK", kBadSourceAddressTokens[i],
+        "VER\0", client_version_.data(),
+        nullptr));
+    const HandshakeFailureReason kRejectReasons[] = {
+      SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+    };
+    CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+  }
+}
+
+TEST_P(CryptoServerTest, BadClientNonce) {
+  // Invalid nonces should be ignored.
+  static const char* kBadNonces[] = {
+    "",
+    "#0000",
+    "#0000000000000000000000000000000000000000",
+  };
+
+  for (size_t i = 0; i < arraysize(kBadNonces); i++) {
+    ShouldSucceed(InchoateClientHello(
+        "CHLO",
+        "NONC", kBadNonces[i],
+        "VER\0", client_version_.data(),
+        nullptr));
+    const HandshakeFailureReason kRejectReasons[] = {
+      SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+    };
+    CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+  }
+}
+
+TEST_P(CryptoServerTest, DowngradeAttack) {
+  if (supported_versions_.size() == 1) {
+    // No downgrade attack is possible if the server only supports one version.
+    return;
+  }
+  // Set the client's preferred version to a supported version that
+  // is not the "current" version (supported_versions_.front()).
+  string bad_version = QuicUtils::TagToString(
+      QuicVersionToQuicTag(supported_versions_.back()));
+
+  ShouldFailMentioning("Downgrade", InchoateClientHello(
+      "CHLO",
+      "VER\0", bad_version.data(),
+      nullptr));
+  const HandshakeFailureReason kRejectReasons[] = {
+    SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, CorruptServerConfig) {
+  // This tests corrupted server config.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", (string(1, 'X') + scid_hex_).c_str(),
+      "#004b5453", srct_hex_.c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", nonce_hex_.c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+  ShouldSucceed(msg);
+  ASSERT_EQ(kREJ, out_.tag());
+  const HandshakeFailureReason kRejectReasons[] = {
+    SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, CorruptSourceAddressToken) {
+  // This tests corrupted source address token.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", (string(1, 'X') + srct_hex_).c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", nonce_hex_.c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+  ShouldSucceed(msg);
+  ASSERT_EQ(kREJ, out_.tag());
+  const HandshakeFailureReason kRejectReasons[] = {
+    SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, CorruptClientNonceAndSourceAddressToken) {
+  // This test corrupts client nonce and source address token.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", (string(1, 'X') + srct_hex_).c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", (string(1, 'X') + nonce_hex_).c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+  ShouldSucceed(msg);
+  ASSERT_EQ(kREJ, out_.tag());
+  const HandshakeFailureReason kRejectReasons[] = {
+    SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+    CLIENT_NONCE_INVALID_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, CorruptMultipleTags) {
+  // This test corrupts client nonce, server nonce and source address token.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", (string(1, 'X') + srct_hex_).c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", (string(1, 'X') + nonce_hex_).c_str(),
+      "SNO\0", (string(1, 'X') + nonce_hex_).c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+  ShouldSucceed(msg);
+  ASSERT_EQ(kREJ, out_.tag());
+  const HandshakeFailureReason kRejectReasons[] = {
+    SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+    CLIENT_NONCE_INVALID_FAILURE,
+    SERVER_NONCE_DECRYPTION_FAILURE,
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+TEST_P(CryptoServerTest, ReplayProtection) {
+  // This tests that disabling replay protection works.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", nonce_hex_.c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+  ShouldSucceed(msg);
+  // The message should be rejected because the strike-register is still
+  // quiescent.
+  ASSERT_EQ(kREJ, out_.tag());
+
+  const HandshakeFailureReason kRejectReasons[] = {
+    CLIENT_NONCE_INVALID_TIME_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+
+  config_.set_replay_protection(false);
+
+  ShouldSucceed(msg);
+  // The message should be accepted now.
+  ASSERT_EQ(kSHLO, out_.tag());
+  CheckServerHello(out_);
+
+  ShouldSucceed(msg);
+  // The message should accepted twice when replay protection is off.
+  ASSERT_EQ(kSHLO, out_.tag());
+  CheckServerHello(out_);
+}
+
+TEST(CryptoServerConfigGenerationTest, Determinism) {
+  // Test that using a deterministic PRNG causes the server-config to be
+  // deterministic.
+
+  MockRandom rand_a, rand_b;
+  const QuicCryptoServerConfig::ConfigOptions options;
+  MockClock clock;
+
+  QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a);
+  QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b);
+  scoped_ptr<CryptoHandshakeMessage> scfg_a(
+      a.AddDefaultConfig(&rand_a, &clock, options));
+  scoped_ptr<CryptoHandshakeMessage> scfg_b(
+      b.AddDefaultConfig(&rand_b, &clock, options));
+
+  ASSERT_EQ(scfg_a->DebugString(), scfg_b->DebugString());
+}
+
+TEST(CryptoServerConfigGenerationTest, SCIDVaries) {
+  // This test ensures that the server config ID varies for different server
+  // configs.
+
+  MockRandom rand_a, rand_b;
+  const QuicCryptoServerConfig::ConfigOptions options;
+  MockClock clock;
+
+  QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a);
+  rand_b.ChangeValue();
+  QuicCryptoServerConfig b(QuicCryptoServerConfig::TESTING, &rand_b);
+  scoped_ptr<CryptoHandshakeMessage> scfg_a(
+      a.AddDefaultConfig(&rand_a, &clock, options));
+  scoped_ptr<CryptoHandshakeMessage> scfg_b(
+      b.AddDefaultConfig(&rand_b, &clock, options));
+
+  StringPiece scid_a, scid_b;
+  EXPECT_TRUE(scfg_a->GetStringPiece(kSCID, &scid_a));
+  EXPECT_TRUE(scfg_b->GetStringPiece(kSCID, &scid_b));
+
+  EXPECT_NE(scid_a, scid_b);
+}
+
+
+TEST(CryptoServerConfigGenerationTest, SCIDIsHashOfServerConfig) {
+  MockRandom rand_a;
+  const QuicCryptoServerConfig::ConfigOptions options;
+  MockClock clock;
+
+  QuicCryptoServerConfig a(QuicCryptoServerConfig::TESTING, &rand_a);
+  scoped_ptr<CryptoHandshakeMessage> scfg(
+      a.AddDefaultConfig(&rand_a, &clock, options));
+
+  StringPiece scid;
+  EXPECT_TRUE(scfg->GetStringPiece(kSCID, &scid));
+  // Need to take a copy of |scid| has we're about to call |Erase|.
+  const string scid_str(scid.as_string());
+
+  scfg->Erase(kSCID);
+  scfg->MarkDirty();
+  const QuicData& serialized(scfg->GetSerialized());
+
+  scoped_ptr<crypto::SecureHash> hash(
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+  hash->Update(serialized.data(), serialized.length());
+  uint8 digest[16];
+  hash->Finish(digest, sizeof(digest));
+
+  ASSERT_EQ(scid.size(), sizeof(digest));
+  EXPECT_EQ(0, memcmp(digest, scid_str.data(), sizeof(digest)));
+}
+
+class CryptoServerTestNoConfig : public CryptoServerTest {
+ public:
+  virtual void SetUp() {
+    // Deliberately don't add a config so that we can test this situation.
+  }
+};
+
+TEST_P(CryptoServerTestNoConfig, DontCrash) {
+  ShouldFailMentioning("No config", InchoateClientHello(
+      "CHLO",
+      "VER\0", client_version_.data(),
+      nullptr));
+
+  const HandshakeFailureReason kRejectReasons[] = {
+    SERVER_CONFIG_INCHOATE_HELLO_FAILURE
+  };
+  CheckRejectReasons(kRejectReasons, arraysize(kRejectReasons));
+}
+
+class AsyncStrikeServerVerificationTest : public CryptoServerTest {
+ protected:
+  AsyncStrikeServerVerificationTest() {
+  }
+
+  virtual void SetUp() {
+    const string kOrbit = "12345678";
+    config_options_.orbit = kOrbit;
+    strike_register_client_ = new DelayedVerifyStrikeRegisterClient(
+        10000,  // strike_register_max_entries
+        static_cast<uint32>(clock_.WallNow().ToUNIXSeconds()),
+        60,  // strike_register_window_secs
+        reinterpret_cast<const uint8 *>(kOrbit.data()),
+        StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
+    config_.SetStrikeRegisterClient(strike_register_client_);
+    CryptoServerTest::SetUp();
+    strike_register_client_->StartDelayingVerification();
+  }
+
+  DelayedVerifyStrikeRegisterClient* strike_register_client_;
+};
+
+TEST_P(AsyncStrikeServerVerificationTest, AsyncReplayProtection) {
+  // This tests async validation with a strike register works.
+  CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+      "CHLO",
+      "AEAD", "AESG",
+      "KEXS", "C255",
+      "SCID", scid_hex_.c_str(),
+      "#004b5453", srct_hex_.c_str(),
+      "PUBS", pub_hex_.c_str(),
+      "NONC", nonce_hex_.c_str(),
+      "VER\0", client_version_.data(),
+      "$padding", static_cast<int>(kClientHelloMinimumSize),
+      nullptr);
+
+  // Clear the message tag.
+  out_.set_tag(0);
+
+  bool called = false;
+  RunValidate(msg, new ValidateCallback(this, true, "", &called));
+  // The verification request was queued.
+  ASSERT_FALSE(called);
+  EXPECT_EQ(0u, out_.tag());
+  EXPECT_EQ(1, strike_register_client_->PendingVerifications());
+
+  // Continue processing the verification request.
+  strike_register_client_->RunPendingVerifications();
+  ASSERT_TRUE(called);
+  EXPECT_EQ(0, strike_register_client_->PendingVerifications());
+  // The message should be accepted now.
+  EXPECT_EQ(kSHLO, out_.tag());
+
+  // Rejected if replayed.
+  RunValidate(msg, new ValidateCallback(this, true, "", &called));
+  // The verification request was queued.
+  ASSERT_FALSE(called);
+  EXPECT_EQ(1, strike_register_client_->PendingVerifications());
+
+  strike_register_client_->RunPendingVerifications();
+  ASSERT_TRUE(called);
+  EXPECT_EQ(0, strike_register_client_->PendingVerifications());
+  // The message should be rejected now.
+  EXPECT_EQ(kREJ, out_.tag());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/crypto_utils.cc b/net/quic/crypto/crypto_utils.cc
new file mode 100644
index 0000000..7e0ab75
--- /dev/null
+++ b/net/quic/crypto/crypto_utils.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_utils.h"
+
+#include "crypto/hkdf.h"
+#include "net/base/net_util.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_time.h"
+#include "url/url_canon.h"
+
+using base::StringPiece;
+using std::numeric_limits;
+using std::string;
+
+namespace net {
+
+// static
+void CryptoUtils::GenerateNonce(QuicWallTime now,
+                                QuicRandom* random_generator,
+                                StringPiece orbit,
+                                string* nonce) {
+  // a 4-byte timestamp + 28 random bytes.
+  nonce->reserve(kNonceSize);
+  nonce->resize(kNonceSize);
+  uint32 gmt_unix_time = now.ToUNIXSeconds();
+  // The time in the nonce must be encoded in big-endian because the
+  // strike-register depends on the nonces being ordered by time.
+  (*nonce)[0] = static_cast<char>(gmt_unix_time >> 24);
+  (*nonce)[1] = static_cast<char>(gmt_unix_time >> 16);
+  (*nonce)[2] = static_cast<char>(gmt_unix_time >> 8);
+  (*nonce)[3] = static_cast<char>(gmt_unix_time);
+
+  size_t bytes_written = sizeof(gmt_unix_time);
+  if (orbit.size() == 8) {
+    memcpy(&(*nonce)[bytes_written], orbit.data(), orbit.size());
+    bytes_written += orbit.size();
+  }
+  random_generator->RandBytes(&(*nonce)[bytes_written],
+                              kNonceSize - bytes_written);
+}
+
+// static
+bool CryptoUtils::IsValidSNI(StringPiece sni) {
+  // TODO(rtenneti): Support RFC2396 hostname.
+  // NOTE: Microsoft does NOT enforce this spec, so if we throw away hostnames
+  // based on the above spec, we may be losing some hostnames that windows
+  // would consider valid. By far the most common hostname character NOT
+  // accepted by the above spec is '_'.
+  url::CanonHostInfo host_info;
+  string canonicalized_host(CanonicalizeHost(sni.as_string(), &host_info));
+  return !host_info.IsIPAddress() &&
+      IsCanonicalizedHostCompliant(canonicalized_host, std::string()) &&
+      sni.find_last_of('.') != string::npos;
+}
+
+// static
+string CryptoUtils::NormalizeHostname(const char* hostname) {
+  url::CanonHostInfo host_info;
+  string host(CanonicalizeHost(hostname, &host_info));
+
+  // Walk backwards over the string, stopping at the first trailing dot.
+  size_t host_end = host.length();
+  while (host_end != 0 && host[host_end - 1] == '.') {
+    host_end--;
+  }
+
+  // Erase the trailing dots.
+  if (host_end != host.length()) {
+    host.erase(host_end, host.length() - host_end);
+  }
+  return host;
+}
+
+// static
+bool CryptoUtils::DeriveKeys(StringPiece premaster_secret,
+                             QuicTag aead,
+                             StringPiece client_nonce,
+                             StringPiece server_nonce,
+                             const string& hkdf_input,
+                             Perspective perspective,
+                             CrypterPair* crypters,
+                             string* subkey_secret) {
+  crypters->encrypter.reset(QuicEncrypter::Create(aead));
+  crypters->decrypter.reset(QuicDecrypter::Create(aead));
+  size_t key_bytes = crypters->encrypter->GetKeySize();
+  size_t nonce_prefix_bytes = crypters->encrypter->GetNoncePrefixSize();
+  size_t subkey_secret_bytes =
+      subkey_secret == nullptr ? 0 : premaster_secret.length();
+
+  StringPiece nonce = client_nonce;
+  string nonce_storage;
+  if (!server_nonce.empty()) {
+    nonce_storage = client_nonce.as_string() + server_nonce.as_string();
+    nonce = nonce_storage;
+  }
+
+  crypto::HKDF hkdf(premaster_secret, nonce, hkdf_input, key_bytes,
+                    nonce_prefix_bytes, subkey_secret_bytes);
+  if (perspective == SERVER) {
+    if (!crypters->encrypter->SetKey(hkdf.server_write_key()) ||
+        !crypters->encrypter->SetNoncePrefix(hkdf.server_write_iv()) ||
+        !crypters->decrypter->SetKey(hkdf.client_write_key()) ||
+        !crypters->decrypter->SetNoncePrefix(hkdf.client_write_iv())) {
+      return false;
+    }
+  } else {
+    if (!crypters->encrypter->SetKey(hkdf.client_write_key()) ||
+        !crypters->encrypter->SetNoncePrefix(hkdf.client_write_iv()) ||
+        !crypters->decrypter->SetKey(hkdf.server_write_key()) ||
+        !crypters->decrypter->SetNoncePrefix(hkdf.server_write_iv())) {
+      return false;
+    }
+  }
+  if (subkey_secret != nullptr) {
+    hkdf.subkey_secret().CopyToString(subkey_secret);
+  }
+
+  return true;
+}
+
+// static
+bool CryptoUtils::ExportKeyingMaterial(StringPiece subkey_secret,
+                                       StringPiece label,
+                                       StringPiece context,
+                                       size_t result_len,
+                                       string* result) {
+  for (size_t i = 0; i < label.length(); i++) {
+    if (label[i] == '\0') {
+      LOG(ERROR) << "ExportKeyingMaterial label may not contain NULs";
+      return false;
+    }
+  }
+  // Create HKDF info input: null-terminated label + length-prefixed context
+  if (context.length() >= numeric_limits<uint32>::max()) {
+    LOG(ERROR) << "Context value longer than 2^32";
+    return false;
+  }
+  uint32 context_length = static_cast<uint32>(context.length());
+  string info = label.as_string();
+  info.push_back('\0');
+  info.append(reinterpret_cast<char*>(&context_length), sizeof(context_length));
+  info.append(context.data(), context.length());
+
+  crypto::HKDF hkdf(subkey_secret,
+                    StringPiece() /* no salt */,
+                    info,
+                    result_len,
+                    0 /* no fixed IV */,
+                    0 /* no subkey secret */);
+  hkdf.client_write_key().CopyToString(result);
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/crypto_utils.h b/net/quic/crypto/crypto_utils.h
new file mode 100644
index 0000000..af3ecbb
--- /dev/null
+++ b/net/quic/crypto/crypto_utils.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Some helpers for quic crypto
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
+#define NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class QuicTime;
+class QuicRandom;
+struct QuicCryptoNegotiatedParameters;
+
+class NET_EXPORT_PRIVATE CryptoUtils {
+ public:
+  enum Perspective {
+    SERVER,
+    CLIENT,
+  };
+
+  // Generates the connection nonce. The nonce is formed as:
+  //   <4 bytes> current time
+  //   <8 bytes> |orbit| (or random if |orbit| is empty)
+  //   <20 bytes> random
+  static void GenerateNonce(QuicWallTime now,
+                            QuicRandom* random_generator,
+                            base::StringPiece orbit,
+                            std::string* nonce);
+
+  // Returns true if the sni is valid, false otherwise.
+  //  (1) disallow IP addresses;
+  //  (2) check that the hostname contains valid characters only; and
+  //  (3) contains at least one dot.
+  static bool IsValidSNI(base::StringPiece sni);
+
+  // Convert hostname to lowercase and remove the trailing '.'.
+  // Returns |hostname|. NormalizeHostname() doesn't support IP address
+  // literals. IsValidSNI() should be called before calling NormalizeHostname().
+  static std::string NormalizeHostname(const char* hostname);
+
+  // DeriveKeys populates |crypters->encrypter|, |crypters->decrypter|, and
+  // |subkey_secret| (optional -- may be null) given the contents of
+  // |premaster_secret|, |client_nonce|, |server_nonce| and |hkdf_input|. |aead|
+  // determines which cipher will be used. |perspective| controls whether the
+  // server's keys are assigned to |encrypter| or |decrypter|. |server_nonce| is
+  // optional and, if non-empty, is mixed into the key derivation.
+  // |subkey_secret| will have the same length as |premaster_secret|.
+  static bool DeriveKeys(base::StringPiece premaster_secret,
+                         QuicTag aead,
+                         base::StringPiece client_nonce,
+                         base::StringPiece server_nonce,
+                         const std::string& hkdf_input,
+                         Perspective perspective,
+                         CrypterPair* crypters,
+                         std::string* subkey_secret);
+
+  // Performs key extraction to derive a new secret of |result_len| bytes
+  // dependent on |subkey_secret|, |label|, and |context|. Returns false if the
+  // parameters are invalid (e.g. |label| contains null bytes); returns true on
+  // success.
+  static bool ExportKeyingMaterial(base::StringPiece subkey_secret,
+                                   base::StringPiece label,
+                                   base::StringPiece context,
+                                   size_t result_len,
+                                   std::string* result);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CryptoUtils);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
diff --git a/net/quic/crypto/crypto_utils_test.cc b/net/quic/crypto/crypto_utils_test.cc
new file mode 100644
index 0000000..fd9f005
--- /dev/null
+++ b/net/quic/crypto/crypto_utils_test.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/crypto_utils.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(CryptoUtilsTest, IsValidSNI) {
+  // IP as SNI.
+  EXPECT_FALSE(CryptoUtils::IsValidSNI("192.168.0.1"));
+  // SNI without any dot.
+  EXPECT_FALSE(CryptoUtils::IsValidSNI("somedomain"));
+  // Invalid RFC2396 hostname
+  // TODO(rtenneti): Support RFC2396 hostname.
+  // EXPECT_FALSE(CryptoUtils::IsValidSNI("some_domain.com"));
+  // An empty string must be invalid otherwise the QUIC client will try sending
+  // it.
+  EXPECT_FALSE(CryptoUtils::IsValidSNI(""));
+
+  // Valid SNI
+  EXPECT_TRUE(CryptoUtils::IsValidSNI("test.google.com"));
+}
+
+TEST(CryptoUtilsTest, NormalizeHostname) {
+  struct {
+    const char *input, *expected;
+  } tests[] = {
+    { "www.google.com", "www.google.com", },
+    { "WWW.GOOGLE.COM", "www.google.com", },
+    { "www.google.com.", "www.google.com", },
+    { "www.google.COM.", "www.google.com", },
+    { "www.google.com..", "www.google.com", },
+    { "www.google.com........", "www.google.com", },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+    EXPECT_EQ(std::string(tests[i].expected),
+              CryptoUtils::NormalizeHostname(tests[i].input));
+  }
+}
+
+TEST(CryptoUtilsTest, TestExportKeyingMaterial) {
+  const struct TestVector {
+    // Input (strings of hexadecimal digits):
+    const char* subkey_secret;
+    const char* label;
+    const char* context;
+    size_t result_len;
+
+    // Expected output (string of hexadecimal digits):
+    const char* expected;  // Null if it should fail.
+  } test_vector[] = {
+    // Try a typical input
+    { "4823c1189ecc40fce888fbb4cf9ae6254f19ba12e6d9af54788f195a6f509ca3",
+      "e934f78d7a71dd85420fceeb8cea0317",
+      "b8d766b5d3c8aba0009c7ed3de553eba53b4de1030ea91383dcdf724cd8b7217",
+      32,
+      "a9979da0d5f1c1387d7cbe68f5c4163ddb445a03c4ad6ee72cb49d56726d679e"
+    },
+    // Don't let the label contain nulls
+    { "14fe51e082ffee7d1b4d8d4ab41f8c55",
+      "3132333435363700",
+      "58585858585858585858585858585858",
+      16,
+      nullptr
+    },
+    // Make sure nulls in the context are fine
+    { "d862c2e36b0a42f7827c67ebc8d44df7",
+      "7a5b95e4e8378123",
+      "4142434445464700",
+      16,
+      "12d418c6d0738a2e4d85b2d0170f76e1"
+    },
+    // ... and give a different result than without
+    { "d862c2e36b0a42f7827c67ebc8d44df7",
+      "7a5b95e4e8378123",
+      "41424344454647",
+      16,
+      "abfa1c479a6e3ffb98a11dee7d196408"
+    },
+    // Try weird lengths
+    { "d0ec8a34f6cc9a8c96",
+      "49711798cc6251",
+      "933d4a2f30d22f089cfba842791116adc121e0",
+      23,
+      "c9a46ed0757bd1812f1f21b4d41e62125fec8364a21db7"
+    },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_vector); i++) {
+    // Decode the test vector.
+    string subkey_secret;
+    string label;
+    string context;
+    ASSERT_TRUE(DecodeHexString(test_vector[i].subkey_secret, &subkey_secret));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].label, &label));
+    ASSERT_TRUE(DecodeHexString(test_vector[i].context, &context));
+    size_t result_len = test_vector[i].result_len;
+    bool expect_ok = test_vector[i].expected != nullptr;
+    string expected;
+    if (expect_ok) {
+      ASSERT_TRUE(DecodeHexString(test_vector[i].expected, &expected));
+    }
+
+    string result;
+    bool ok = CryptoUtils::ExportKeyingMaterial(subkey_secret,
+                                                label,
+                                                context,
+                                                result_len,
+                                                &result);
+    EXPECT_EQ(expect_ok, ok);
+    if (expect_ok) {
+      EXPECT_EQ(result_len, result.length());
+      test::CompareCharArraysWithHexError("HKDF output",
+                                          result.data(),
+                                          result.length(),
+                                          expected.data(),
+                                          expected.length());
+    }
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/curve25519_key_exchange.cc b/net/quic/crypto/curve25519_key_exchange.cc
new file mode 100644
index 0000000..88eb053
--- /dev/null
+++ b/net/quic/crypto/curve25519_key_exchange.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/curve25519_key_exchange.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "crypto/curve25519.h"
+#include "net/quic/crypto/quic_random.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+Curve25519KeyExchange::Curve25519KeyExchange() {}
+
+Curve25519KeyExchange::~Curve25519KeyExchange() {}
+
+// static
+Curve25519KeyExchange* Curve25519KeyExchange::New(
+    const StringPiece& private_key) {
+  Curve25519KeyExchange* ka;
+  // We don't want to #include the NaCl headers in the public header file, so
+  // we use literals for the sizes of private_key_ and public_key_. Here we
+  // assert that those values are equal to the values from the NaCl header.
+  COMPILE_ASSERT(
+      sizeof(ka->private_key_) == crypto::curve25519::kScalarBytes,
+      header_out_of_sync);
+  COMPILE_ASSERT(sizeof(ka->public_key_) == crypto::curve25519::kBytes,
+                 header_out_of_sync);
+
+  if (private_key.size() != crypto::curve25519::kScalarBytes) {
+    return nullptr;
+  }
+
+  ka = new Curve25519KeyExchange();
+  memcpy(ka->private_key_, private_key.data(),
+         crypto::curve25519::kScalarBytes);
+  crypto::curve25519::ScalarBaseMult(ka->private_key_, ka->public_key_);
+  return ka;
+}
+
+// static
+string Curve25519KeyExchange::NewPrivateKey(QuicRandom* rand) {
+  uint8 private_key[crypto::curve25519::kScalarBytes];
+  rand->RandBytes(private_key, sizeof(private_key));
+
+  // This makes |private_key| a valid scalar, as specified on
+  // http://cr.yp.to/ecdh.html
+  private_key[0] &= 248;
+  private_key[31] &= 127;
+  private_key[31] |= 64;
+  return string(reinterpret_cast<char*>(private_key), sizeof(private_key));
+}
+
+KeyExchange* Curve25519KeyExchange::NewKeyPair(QuicRandom* rand) const {
+  const string private_value = NewPrivateKey(rand);
+  return Curve25519KeyExchange::New(private_value);
+}
+
+bool Curve25519KeyExchange::CalculateSharedKey(
+    const StringPiece& peer_public_value,
+    string* out_result) const {
+  if (peer_public_value.size() != crypto::curve25519::kBytes) {
+    return false;
+  }
+
+  uint8 result[crypto::curve25519::kBytes];
+  crypto::curve25519::ScalarMult(
+      private_key_,
+      reinterpret_cast<const uint8*>(peer_public_value.data()),
+      result);
+  out_result->assign(reinterpret_cast<char*>(result), sizeof(result));
+
+  return true;
+}
+
+StringPiece Curve25519KeyExchange::public_value() const {
+  return StringPiece(reinterpret_cast<const char*>(public_key_),
+                     sizeof(public_key_));
+}
+
+QuicTag Curve25519KeyExchange::tag() const { return kC255; }
+
+}  // namespace net
diff --git a/net/quic/crypto/curve25519_key_exchange.h b/net/quic/crypto/curve25519_key_exchange.h
new file mode 100644
index 0000000..93288f6
--- /dev/null
+++ b/net/quic/crypto/curve25519_key_exchange.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/key_exchange.h"
+
+namespace net {
+
+class QuicRandom;
+
+// Curve25519KeyExchange implements a KeyExchange using elliptic-curve
+// Diffie-Hellman on curve25519. See http://cr.yp.to/ecdh.html
+class NET_EXPORT_PRIVATE Curve25519KeyExchange : public KeyExchange {
+ public:
+  virtual ~Curve25519KeyExchange();
+
+  // New creates a new object from a private key. If the private key is
+  // invalid, nullptr is returned.
+  static Curve25519KeyExchange* New(const base::StringPiece& private_key);
+
+  // NewPrivateKey returns a private key, generated from |rand|, suitable for
+  // passing to |New|.
+  static std::string NewPrivateKey(QuicRandom* rand);
+
+  // KeyExchange interface.
+  virtual KeyExchange* NewKeyPair(QuicRandom* rand) const OVERRIDE;
+  virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+                                  std::string* shared_key) const OVERRIDE;
+  virtual base::StringPiece public_value() const OVERRIDE;
+  virtual QuicTag tag() const OVERRIDE;
+
+ private:
+  Curve25519KeyExchange();
+
+  uint8 private_key_[32];
+  uint8 public_key_[32];
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
diff --git a/net/quic/crypto/curve25519_key_exchange_test.cc b/net/quic/crypto/curve25519_key_exchange_test.cc
new file mode 100644
index 0000000..93ef630
--- /dev/null
+++ b/net/quic/crypto/curve25519_key_exchange_test.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/curve25519_key_exchange.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/quic_random.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+// SharedKey just tests that the basic key exchange identity holds: that both
+// parties end up with the same key.
+TEST(Curve25519KeyExchange, SharedKey) {
+  QuicRandom* const rand = QuicRandom::GetInstance();
+
+  for (int i = 0; i < 5; i++) {
+    const string alice_key(Curve25519KeyExchange::NewPrivateKey(rand));
+    const string bob_key(Curve25519KeyExchange::NewPrivateKey(rand));
+
+    scoped_ptr<Curve25519KeyExchange> alice(
+        Curve25519KeyExchange::New(alice_key));
+    scoped_ptr<Curve25519KeyExchange> bob(Curve25519KeyExchange::New(bob_key));
+
+    const StringPiece alice_public(alice->public_value());
+    const StringPiece bob_public(bob->public_value());
+
+    string alice_shared, bob_shared;
+    ASSERT_TRUE(alice->CalculateSharedKey(bob_public, &alice_shared));
+    ASSERT_TRUE(bob->CalculateSharedKey(alice_public, &bob_shared));
+    ASSERT_EQ(alice_shared, bob_shared);
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/ephemeral_key_source.h b/net/quic/crypto/ephemeral_key_source.h
new file mode 100644
index 0000000..2700be0
--- /dev/null
+++ b/net/quic/crypto/ephemeral_key_source.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
+#define NET_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class KeyExchange;
+class QuicRandom;
+
+// EphemeralKeySource manages and rotates ephemeral keys as they can be reused
+// for several connections in a short space of time. Since the implementation
+// of this may involve locking or thread-local data, this interface abstracts
+// that away.
+class NET_EXPORT_PRIVATE EphemeralKeySource {
+ public:
+  virtual ~EphemeralKeySource() {}
+
+  // CalculateForwardSecureKey generates an ephemeral public/private key pair
+  // using the algorithm |key_exchange|, sets |*public_value| to the public key
+  // and returns the shared key between |peer_public_value| and the private
+  // key. |*public_value| will be sent to the peer to be used with the peer's
+  // private key.
+  virtual std::string CalculateForwardSecureKey(
+      const KeyExchange* key_exchange,
+      QuicRandom* rand,
+      QuicTime now,
+      base::StringPiece peer_public_value,
+      std::string* public_value) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
diff --git a/net/quic/crypto/key_exchange.h b/net/quic/crypto/key_exchange.h
new file mode 100644
index 0000000..8690f0e
--- /dev/null
+++ b/net/quic/crypto/key_exchange.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+class QuicRandom;
+
+// KeyExchange is an abstract class that provides an interface to a
+// key-exchange primitive.
+class NET_EXPORT_PRIVATE KeyExchange {
+ public:
+  virtual ~KeyExchange() {}
+
+  // NewKeyPair generates a new public, private key pair. The caller takes
+  // ownership of the return value. (This is intended for servers that need to
+  // generate forward-secure keys.)
+  virtual KeyExchange* NewKeyPair(QuicRandom* rand) const = 0;
+
+  // CalculateSharedKey computes the shared key between the local private key
+  // (which is implicitly known by a KeyExchange object) and a public value
+  // from the peer.
+  virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+                                  std::string* shared_key) const = 0;
+
+  // public_value returns the local public key which can be sent to a peer in
+  // order to complete a key exchange. The returned StringPiece is a reference
+  // to a member of the KeyExchange and is only valid for as long as the
+  // KeyExchange exists.
+  virtual base::StringPiece public_value() const = 0;
+
+  // tag returns the tag value that identifies this key exchange function.
+  virtual QuicTag tag() const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_KEY_EXCHANGE_H_
diff --git a/net/quic/crypto/local_strike_register_client.cc b/net/quic/crypto/local_strike_register_client.cc
new file mode 100644
index 0000000..71d2224
--- /dev/null
+++ b/net/quic/crypto/local_strike_register_client.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/local_strike_register_client.h"
+
+#include "net/quic/crypto/crypto_protocol.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+LocalStrikeRegisterClient::LocalStrikeRegisterClient(
+    unsigned max_entries,
+    uint32 current_time_external,
+    uint32 window_secs,
+    const uint8 orbit[8],
+    StrikeRegister::StartupType startup)
+    : strike_register_(max_entries, current_time_external,  window_secs,
+                       orbit, startup) {
+}
+
+bool LocalStrikeRegisterClient::IsKnownOrbit(StringPiece orbit) const {
+  base::AutoLock lock(m_);
+  if (orbit.length() != kOrbitSize) {
+    return false;
+  }
+  return memcmp(orbit.data(), strike_register_.orbit(), kOrbitSize) == 0;
+}
+
+void LocalStrikeRegisterClient::VerifyNonceIsValidAndUnique(
+    StringPiece nonce,
+    QuicWallTime now,
+    ResultCallback* cb) {
+  InsertStatus nonce_error;
+  if (nonce.length() != kNonceSize) {
+    nonce_error = NONCE_INVALID_FAILURE;
+  } else {
+    base::AutoLock lock(m_);
+    nonce_error = strike_register_.Insert(
+        reinterpret_cast<const uint8*>(nonce.data()),
+        static_cast<uint32>(now.ToUNIXSeconds()));
+  }
+
+  // m_ must not be held when the ResultCallback runs.
+  cb->Run((nonce_error == NONCE_OK), nonce_error);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/local_strike_register_client.h b/net/quic/crypto/local_strike_register_client.h
new file mode 100644
index 0000000..fe8ae93
--- /dev/null
+++ b/net/quic/crypto/local_strike_register_client.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_LOCAL_STRIKE_REGISTER_CLIENT_H_
+#define NET_QUIC_CRYPTO_LOCAL_STRIKE_REGISTER_CLIENT_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/strike_register.h"
+#include "net/quic/crypto/strike_register_client.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// StrikeRegisterClient implementation that wraps a local in-memory
+// strike register.
+class NET_EXPORT_PRIVATE LocalStrikeRegisterClient
+    : public StrikeRegisterClient {
+ public:
+  LocalStrikeRegisterClient(unsigned max_entries,
+                            uint32 current_time_external,
+                            uint32 window_secs,
+                            const uint8 orbit[8],
+                            StrikeRegister::StartupType startup);
+
+  virtual bool IsKnownOrbit(base::StringPiece orbit) const OVERRIDE;
+  virtual void VerifyNonceIsValidAndUnique(base::StringPiece nonce,
+                                           QuicWallTime now,
+                                           ResultCallback* cb) OVERRIDE;
+
+ private:
+  mutable base::Lock m_;
+  StrikeRegister strike_register_;
+
+  DISALLOW_COPY_AND_ASSIGN(LocalStrikeRegisterClient);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_LOCAL_STRIKE_REGISTER_CLIENT_H_
diff --git a/net/quic/crypto/local_strike_register_client_test.cc b/net/quic/crypto/local_strike_register_client_test.cc
new file mode 100644
index 0000000..1a3b6f9
--- /dev/null
+++ b/net/quic/crypto/local_strike_register_client_test.cc
@@ -0,0 +1,138 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/local_strike_register_client.h"
+
+#include <memory>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/sys_byteorder.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+class RecordResultCallback : public StrikeRegisterClient::ResultCallback {
+ public:
+  // RecordResultCallback stores the argument to RunImpl in
+  // |*saved_value| and sets |*called| to true.  The callback is self
+  // deleting.
+  RecordResultCallback(bool* called,
+                       bool* saved_value,
+                       InsertStatus* saved_nonce_error)
+      : called_(called),
+        saved_value_(saved_value),
+        saved_nonce_error_(saved_nonce_error) {
+    *called_ = false;
+  }
+
+ protected:
+  virtual void RunImpl(bool nonce_is_valid_and_unique,
+                       InsertStatus nonce_error) OVERRIDE {
+    *called_ = true;
+    *saved_value_ = nonce_is_valid_and_unique;
+    *saved_nonce_error_ = nonce_error;
+  }
+
+ private:
+  bool* called_;
+  bool* saved_value_;
+  InsertStatus* saved_nonce_error_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordResultCallback);
+};
+
+const uint8 kOrbit[] = "\x12\x34\x56\x78\x9A\xBC\xDE\xF0";
+const uint32 kCurrentTimeExternalSecs = 12345678;
+size_t kMaxEntries = 100;
+uint32 kWindowSecs = 60;
+
+class LocalStrikeRegisterClientTest : public ::testing::Test {
+ protected:
+  LocalStrikeRegisterClientTest() {
+  }
+
+  virtual void SetUp() {
+    strike_register_.reset(new LocalStrikeRegisterClient(
+        kMaxEntries, kCurrentTimeExternalSecs, kWindowSecs, kOrbit,
+        net::StrikeRegister::NO_STARTUP_PERIOD_NEEDED));
+  }
+
+  scoped_ptr<LocalStrikeRegisterClient> strike_register_;
+};
+
+TEST_F(LocalStrikeRegisterClientTest, CheckOrbit) {
+  EXPECT_TRUE(strike_register_->IsKnownOrbit(
+      StringPiece(reinterpret_cast<const char*>(kOrbit), kOrbitSize)));
+  EXPECT_FALSE(strike_register_->IsKnownOrbit(
+      StringPiece(reinterpret_cast<const char*>(kOrbit), kOrbitSize - 1)));
+  EXPECT_FALSE(strike_register_->IsKnownOrbit(
+      StringPiece(reinterpret_cast<const char*>(kOrbit), kOrbitSize + 1)));
+  EXPECT_FALSE(strike_register_->IsKnownOrbit(
+      StringPiece(reinterpret_cast<const char*>(kOrbit) + 1, kOrbitSize)));
+}
+
+TEST_F(LocalStrikeRegisterClientTest, IncorrectNonceLength) {
+  string valid_nonce;
+  uint32 norder = htonl(kCurrentTimeExternalSecs);
+  valid_nonce.assign(reinterpret_cast<const char*>(&norder), sizeof(norder));
+  valid_nonce.append(string(reinterpret_cast<const char*>(kOrbit), kOrbitSize));
+  valid_nonce.append(string(20, '\x17'));  // 20 'random' bytes.
+
+  {
+    // Validation fails if you remove a byte from the nonce.
+    bool called = false;
+    bool is_valid = false;
+    InsertStatus nonce_error = NONCE_UNKNOWN_FAILURE;
+    string short_nonce = valid_nonce.substr(0, valid_nonce.length() - 1);
+    strike_register_->VerifyNonceIsValidAndUnique(
+        short_nonce,
+        QuicWallTime::FromUNIXSeconds(kCurrentTimeExternalSecs),
+        new RecordResultCallback(&called, &is_valid, &nonce_error));
+    EXPECT_TRUE(called);
+    EXPECT_FALSE(is_valid);
+    EXPECT_EQ(NONCE_INVALID_FAILURE, nonce_error);
+  }
+
+  {
+    // Validation fails if you add a byte to the nonce.
+    bool called = false;
+    bool is_valid = false;
+    InsertStatus nonce_error = NONCE_UNKNOWN_FAILURE;
+    string long_nonce(valid_nonce);
+    long_nonce.append("a");
+    strike_register_->VerifyNonceIsValidAndUnique(
+        long_nonce,
+        QuicWallTime::FromUNIXSeconds(kCurrentTimeExternalSecs),
+        new RecordResultCallback(&called, &is_valid, &nonce_error));
+    EXPECT_TRUE(called);
+    EXPECT_FALSE(is_valid);
+    EXPECT_EQ(NONCE_INVALID_FAILURE, nonce_error);
+  }
+
+  {
+    // Verify that the base nonce validates was valid.
+    bool called = false;
+    bool is_valid = false;
+    InsertStatus nonce_error = NONCE_UNKNOWN_FAILURE;
+    strike_register_->VerifyNonceIsValidAndUnique(
+        valid_nonce,
+        QuicWallTime::FromUNIXSeconds(kCurrentTimeExternalSecs),
+        new RecordResultCallback(&called, &is_valid, &nonce_error));
+    EXPECT_TRUE(called);
+    EXPECT_TRUE(is_valid);
+    EXPECT_EQ(NONCE_OK, nonce_error);
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/null_decrypter.cc b/net/quic/crypto/null_decrypter.cc
new file mode 100644
index 0000000..77aee4c
--- /dev/null
+++ b/net/quic/crypto/null_decrypter.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/null_decrypter.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/quic_data_reader.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+NullDecrypter::NullDecrypter() {}
+
+bool NullDecrypter::SetKey(StringPiece key) { return key.empty(); }
+
+bool NullDecrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  return nonce_prefix.empty();
+}
+
+bool NullDecrypter::Decrypt(StringPiece /*nonce*/,
+                            StringPiece associated_data,
+                            StringPiece ciphertext,
+                            unsigned char* output,
+                            size_t* output_length) {
+  QuicDataReader reader(ciphertext.data(), ciphertext.length());
+
+  uint128 hash;
+  if (!ReadHash(&reader, &hash)) {
+    return false;
+  }
+
+  StringPiece plaintext = reader.ReadRemainingPayload();
+
+  // TODO(rch): avoid buffer copy here
+  string buffer = associated_data.as_string();
+  plaintext.AppendToString(&buffer);
+  if (hash != ComputeHash(buffer)) {
+    return false;
+  }
+  memcpy(output, plaintext.data(), plaintext.length());
+  *output_length = plaintext.length();
+  return true;
+}
+
+QuicData* NullDecrypter::DecryptPacket(QuicPacketSequenceNumber /*seq_number*/,
+                                       StringPiece associated_data,
+                                       StringPiece ciphertext) {
+  // It's worth duplicating |Decrypt|, above, in order to save a copy by using
+  // the shared-data QuicData constructor directly.
+  QuicDataReader reader(ciphertext.data(), ciphertext.length());
+
+  uint128 hash;
+  if (!ReadHash(&reader, &hash)) {
+    return nullptr;
+  }
+
+  StringPiece plaintext = reader.ReadRemainingPayload();
+
+  // TODO(rch): avoid buffer copy here
+  string buffer = associated_data.as_string();
+  plaintext.AppendToString(&buffer);
+
+  if (hash != ComputeHash(buffer)) {
+    return nullptr;
+  }
+  return new QuicData(plaintext.data(), plaintext.length());
+}
+
+StringPiece NullDecrypter::GetKey() const { return StringPiece(); }
+
+StringPiece NullDecrypter::GetNoncePrefix() const { return StringPiece(); }
+
+bool NullDecrypter::ReadHash(QuicDataReader* reader, uint128* hash) {
+  uint64 lo;
+  uint32 hi;
+  if (!reader->ReadUInt64(&lo) ||
+      !reader->ReadUInt32(&hi)) {
+    return false;
+  }
+  *hash = hi;
+  *hash <<= 64;
+  *hash += lo;
+  return true;
+}
+
+uint128 NullDecrypter::ComputeHash(const string& data) const {
+  uint128 correct_hash = QuicUtils::FNV1a_128_Hash(data.data(), data.length());
+  uint128 mask(GG_UINT64_C(0x0), GG_UINT64_C(0xffffffff));
+  mask <<= 96;
+  correct_hash &= ~mask;
+  return correct_hash;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/null_decrypter.h b/net/quic/crypto/null_decrypter.h
new file mode 100644
index 0000000..2bc2fe8
--- /dev/null
+++ b/net/quic/crypto/null_decrypter.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/quic_decrypter.h"
+
+namespace net {
+
+class QuicDataReader;
+
+// A NullDecrypter is a QuicDecrypter used before a crypto negotiation
+// has occurred.  It does not actually decrypt the payload, but does
+// verify a hash (fnv128) over both the payload and associated data.
+class NET_EXPORT_PRIVATE NullDecrypter : public QuicDecrypter {
+ public:
+  NullDecrypter();
+  virtual ~NullDecrypter() {}
+
+  // QuicDecrypter implementation
+  virtual bool SetKey(base::StringPiece key) OVERRIDE;
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+  virtual bool Decrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece ciphertext,
+                       unsigned char* output,
+                       size_t* output_length) OVERRIDE;
+  virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece ciphertext) OVERRIDE;
+  virtual base::StringPiece GetKey() const OVERRIDE;
+  virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ private:
+  bool ReadHash(QuicDataReader* reader, uint128* hash);
+  uint128 ComputeHash(const std::string& data) const;
+
+  DISALLOW_COPY_AND_ASSIGN(NullDecrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
diff --git a/net/quic/crypto/null_decrypter_test.cc b/net/quic/crypto/null_decrypter_test.cc
new file mode 100644
index 0000000..173ecde
--- /dev/null
+++ b/net/quic/crypto/null_decrypter_test.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/null_decrypter.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+class NullDecrypterTest : public ::testing::TestWithParam<bool> {
+};
+
+TEST_F(NullDecrypterTest, Decrypt) {
+  unsigned char expected[] = {
+    // fnv hash
+    0xa0, 0x6f, 0x44, 0x8a,
+    0x44, 0xf8, 0x18, 0x3b,
+    0x47, 0x91, 0xb2, 0x13,
+    // payload
+    'g',  'o',  'o',  'd',
+    'b',  'y',  'e',  '!',
+  };
+  const char* data = reinterpret_cast<const char*>(expected);
+  size_t len = arraysize(expected);
+  NullDecrypter decrypter;
+  scoped_ptr<QuicData> decrypted(
+      decrypter.DecryptPacket(0, "hello world!", StringPiece(data, len)));
+  ASSERT_TRUE(decrypted.get());
+  EXPECT_EQ("goodbye!", decrypted->AsStringPiece());
+}
+
+TEST_F(NullDecrypterTest, BadHash) {
+  unsigned char expected[] = {
+    // fnv hash
+    0x46, 0x11, 0xea, 0x5f,
+    0xcf, 0x1d, 0x66, 0x5b,
+    0xba, 0xf0, 0xbc, 0xfd,
+    // payload
+    'g',  'o',  'o',  'd',
+    'b',  'y',  'e',  '!',
+  };
+  const char* data = reinterpret_cast<const char*>(expected);
+  size_t len = arraysize(expected);
+  NullDecrypter decrypter;
+  scoped_ptr<QuicData> decrypted(
+      decrypter.DecryptPacket(0, "hello world!", StringPiece(data, len)));
+  ASSERT_FALSE(decrypted.get());
+}
+
+TEST_F(NullDecrypterTest, ShortInput) {
+  unsigned char expected[] = {
+    // fnv hash (truncated)
+    0x46, 0x11, 0xea, 0x5f,
+    0xcf, 0x1d, 0x66, 0x5b,
+    0xba, 0xf0, 0xbc,
+  };
+  const char* data = reinterpret_cast<const char*>(expected);
+  size_t len = arraysize(expected);
+  NullDecrypter decrypter;
+  scoped_ptr<QuicData> decrypted(
+      decrypter.DecryptPacket(0, "hello world!", StringPiece(data, len)));
+  ASSERT_FALSE(decrypted.get());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/null_encrypter.cc b/net/quic/crypto/null_encrypter.cc
new file mode 100644
index 0000000..5f1a85b
--- /dev/null
+++ b/net/quic/crypto/null_encrypter.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/quic_data_writer.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+const size_t kHashSizeShort = 12;  // size of uint128 serialized short
+
+NullEncrypter::NullEncrypter() {}
+
+bool NullEncrypter::SetKey(StringPiece key) { return key.empty(); }
+
+bool NullEncrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+  return nonce_prefix.empty();
+}
+
+bool NullEncrypter::Encrypt(
+    StringPiece /*nonce*/,
+    StringPiece associated_data,
+    StringPiece plaintext,
+    unsigned char* output) {
+  string buffer = associated_data.as_string();
+  plaintext.AppendToString(&buffer);
+  uint128 hash = QuicUtils::FNV1a_128_Hash(buffer.data(), buffer.length());
+  QuicUtils::SerializeUint128Short(hash, output);
+  memcpy(output + GetHashLength(), plaintext.data(), plaintext.size());
+  return true;
+}
+
+QuicData* NullEncrypter::EncryptPacket(
+    QuicPacketSequenceNumber /*sequence_number*/,
+    StringPiece associated_data,
+    StringPiece plaintext) {
+  const size_t len = plaintext.size() + GetHashLength();
+  uint8* buffer = new uint8[len];
+  Encrypt(StringPiece(), associated_data, plaintext, buffer);
+  return new QuicData(reinterpret_cast<char*>(buffer), len, true);
+}
+
+size_t NullEncrypter::GetKeySize() const { return 0; }
+
+size_t NullEncrypter::GetNoncePrefixSize() const { return 0; }
+
+size_t NullEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+  return ciphertext_size - GetHashLength();
+}
+
+size_t NullEncrypter::GetCiphertextSize(size_t plaintext_size) const {
+  return plaintext_size + GetHashLength();
+}
+
+StringPiece NullEncrypter::GetKey() const { return StringPiece(); }
+
+StringPiece NullEncrypter::GetNoncePrefix() const { return StringPiece(); }
+
+size_t NullEncrypter::GetHashLength() const {
+  return kHashSizeShort;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/null_encrypter.h b/net/quic/crypto/null_encrypter.h
new file mode 100644
index 0000000..1bcdff5
--- /dev/null
+++ b/net/quic/crypto/null_encrypter.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+namespace net {
+
+// A NullEncrypter is a QuicEncrypter used before a crypto negotiation
+// has occurred.  It does not actually encrypt the payload, but does
+// generate a MAC (fnv128) over both the payload and associated data.
+class NET_EXPORT_PRIVATE NullEncrypter : public QuicEncrypter {
+ public:
+  NullEncrypter();
+  virtual ~NullEncrypter() {}
+
+  // QuicEncrypter implementation
+  virtual bool SetKey(base::StringPiece key) OVERRIDE;
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+  virtual bool Encrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece plaintext,
+                       unsigned char* output) OVERRIDE;
+  virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece plaintext) OVERRIDE;
+  virtual size_t GetKeySize() const OVERRIDE;
+  virtual size_t GetNoncePrefixSize() const OVERRIDE;
+  virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE;
+  virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE;
+  virtual base::StringPiece GetKey() const OVERRIDE;
+  virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ private:
+  size_t GetHashLength() const;
+
+  DISALLOW_COPY_AND_ASSIGN(NullEncrypter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
diff --git a/net/quic/crypto/null_encrypter_test.cc b/net/quic/crypto/null_encrypter_test.cc
new file mode 100644
index 0000000..4f4dae7
--- /dev/null
+++ b/net/quic/crypto/null_encrypter_test.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+class NullEncrypterTest : public ::testing::TestWithParam<bool> {
+};
+
+TEST_F(NullEncrypterTest, Encrypt) {
+  unsigned char expected[] = {
+    // fnv hash
+    0xa0, 0x6f, 0x44, 0x8a,
+    0x44, 0xf8, 0x18, 0x3b,
+    0x47, 0x91, 0xb2, 0x13,
+    // payload
+    'g',  'o',  'o',  'd',
+    'b',  'y',  'e',  '!',
+  };
+  NullEncrypter encrypter;
+  scoped_ptr<QuicData> encrypted(
+      encrypter.EncryptPacket(0, "hello world!", "goodbye!"));
+  ASSERT_TRUE(encrypted.get());
+  test::CompareCharArraysWithHexError(
+      "encrypted data", encrypted->data(), encrypted->length(),
+      reinterpret_cast<const char*>(expected),
+      arraysize(expected));
+}
+
+TEST_F(NullEncrypterTest, GetMaxPlaintextSize) {
+  NullEncrypter encrypter;
+  EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012));
+  EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112));
+  EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22));
+}
+
+TEST_F(NullEncrypterTest, GetCiphertextSize) {
+  NullEncrypter encrypter;
+  EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000));
+  EXPECT_EQ(112u, encrypter.GetCiphertextSize(100));
+  EXPECT_EQ(22u, encrypter.GetCiphertextSize(10));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/p256_key_exchange.h b/net/quic/crypto/p256_key_exchange.h
new file mode 100644
index 0000000..ce5a0d4
--- /dev/null
+++ b/net/quic/crypto/p256_key_exchange.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/key_exchange.h"
+
+#if defined(USE_OPENSSL)
+#include "crypto/openssl_util.h"
+#include "crypto/scoped_openssl_types.h"
+#else
+#include "crypto/ec_private_key.h"
+#include "crypto/scoped_nss_types.h"
+#endif
+
+namespace net {
+
+// P256KeyExchange implements a KeyExchange using elliptic-curve
+// Diffie-Hellman on NIST P-256.
+class NET_EXPORT_PRIVATE P256KeyExchange : public KeyExchange {
+ public:
+  virtual ~P256KeyExchange();
+
+  // New creates a new key exchange object from a private key. If
+  // |private_key| is invalid, nullptr is returned.
+  static P256KeyExchange* New(base::StringPiece private_key);
+
+  // |NewPrivateKey| returns a private key, suitable for passing to |New|.
+  // If |NewPrivateKey| can't generate a private key, it returns an empty
+  // string.
+  static std::string NewPrivateKey();
+
+  // KeyExchange interface.
+  virtual KeyExchange* NewKeyPair(QuicRandom* rand) const OVERRIDE;
+  virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+                                  std::string* shared_key) const OVERRIDE;
+  virtual base::StringPiece public_value() const OVERRIDE;
+  virtual QuicTag tag() const OVERRIDE;
+
+ private:
+  enum {
+    // A P-256 field element consists of 32 bytes.
+    kP256FieldBytes = 32,
+    // A P-256 point in uncompressed form consists of 0x04 (to denote
+    // that the point is uncompressed) followed by two, 32-byte field
+    // elements.
+    kUncompressedP256PointBytes = 1 + 2 * kP256FieldBytes,
+    // The first byte in an uncompressed P-256 point.
+    kUncompressedECPointForm = 0x04,
+  };
+
+#if defined(USE_OPENSSL)
+  // P256KeyExchange takes ownership of |private_key|, and expects
+  // |public_key| consists of |kUncompressedP256PointBytes| bytes.
+  P256KeyExchange(EC_KEY* private_key, const uint8* public_key);
+
+  crypto::ScopedEC_KEY private_key_;
+#else
+  // P256KeyExchange takes ownership of |key_pair|, and expects
+  // |public_key| consists of |kUncompressedP256PointBytes| bytes.
+  P256KeyExchange(crypto::ECPrivateKey* key_pair, const uint8* public_key);
+
+  scoped_ptr<crypto::ECPrivateKey> key_pair_;
+#endif
+  // The public key stored as an uncompressed P-256 point.
+  uint8 public_key_[kUncompressedP256PointBytes];
+
+  DISALLOW_COPY_AND_ASSIGN(P256KeyExchange);
+};
+
+}  // namespace net
+#endif  // NET_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
diff --git a/net/quic/crypto/p256_key_exchange_nss.cc b/net/quic/crypto/p256_key_exchange_nss.cc
new file mode 100644
index 0000000..73aa03d
--- /dev/null
+++ b/net/quic/crypto/p256_key_exchange_nss.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/p256_key_exchange.h"
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// Password used by |NewPrivateKey| to encrypt exported EC private keys.
+// This is not used to provide any security, but to workaround NSS being
+// unwilling to export unencrypted EC keys. Note that SPDY and ChannelID
+// use the same approach.
+const char kExportPassword[] = "";
+
+// Convert StringPiece to vector of uint8.
+static vector<uint8> StringPieceToVector(StringPiece piece) {
+  return vector<uint8>(piece.data(), piece.data() + piece.length());
+}
+
+}  // namespace
+
+P256KeyExchange::P256KeyExchange(crypto::ECPrivateKey* key_pair,
+                                 const uint8* public_key)
+    : key_pair_(key_pair) {
+  memcpy(public_key_, public_key, sizeof(public_key_));
+}
+
+P256KeyExchange::~P256KeyExchange() {
+}
+
+// static
+P256KeyExchange* P256KeyExchange::New(StringPiece key) {
+  if (key.size() < 2) {
+    DVLOG(1) << "Key pair is too small.";
+    return nullptr;
+  }
+
+  const uint8* data = reinterpret_cast<const uint8*>(key.data());
+  size_t size = static_cast<size_t>(data[0]) |
+                (static_cast<size_t>(data[1]) << 8);
+  key.remove_prefix(2);
+  if (key.size() < size) {
+    DVLOG(1) << "Key pair does not contain key material.";
+    return nullptr;
+  }
+
+  StringPiece private_piece(key.data(), size);
+  key.remove_prefix(size);
+  if (key.empty()) {
+    DVLOG(1) << "Key pair does not contain public key.";
+    return nullptr;
+  }
+
+  StringPiece public_piece(key);
+
+  scoped_ptr<crypto::ECPrivateKey> key_pair(
+      crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+          kExportPassword,
+          // TODO(thaidn): fix this interface to avoid copying secrets.
+          StringPieceToVector(private_piece),
+          StringPieceToVector(public_piece)));
+
+  if (!key_pair.get()) {
+    DVLOG(1) << "Can't decrypt private key.";
+    return nullptr;
+  }
+
+  // Perform some sanity checks on the public key.
+  SECKEYPublicKey* public_key = key_pair->public_key();
+  if (public_key->keyType != ecKey ||
+      public_key->u.ec.publicValue.len != kUncompressedP256PointBytes ||
+      !public_key->u.ec.publicValue.data ||
+      public_key->u.ec.publicValue.data[0] != kUncompressedECPointForm) {
+    DVLOG(1) << "Key is invalid.";
+    return nullptr;
+  }
+
+  // Ensure that the key is using the correct curve, i.e., NIST P-256.
+  const SECOidData* oid_data = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
+  if (!oid_data) {
+    DVLOG(1) << "Can't get P-256's OID.";
+    return nullptr;
+  }
+
+  if (public_key->u.ec.DEREncodedParams.len != oid_data->oid.len + 2 ||
+      !public_key->u.ec.DEREncodedParams.data ||
+      public_key->u.ec.DEREncodedParams.data[0] != SEC_ASN1_OBJECT_ID ||
+      public_key->u.ec.DEREncodedParams.data[1] != oid_data->oid.len ||
+      memcmp(public_key->u.ec.DEREncodedParams.data + 2,
+             oid_data->oid.data, oid_data->oid.len) != 0) {
+    DVLOG(1) << "Key is invalid.";
+  }
+
+  return new P256KeyExchange(key_pair.release(),
+                             public_key->u.ec.publicValue.data);
+}
+
+// static
+string P256KeyExchange::NewPrivateKey() {
+  scoped_ptr<crypto::ECPrivateKey> key_pair(crypto::ECPrivateKey::Create());
+
+  if (!key_pair.get()) {
+    DVLOG(1) << "Can't generate new key pair.";
+    return string();
+  }
+
+  vector<uint8> private_key;
+  if (!key_pair->ExportEncryptedPrivateKey(kExportPassword,
+                                           1 /* iteration */,
+                                           &private_key)) {
+    DVLOG(1) << "Can't export private key.";
+    return string();
+  }
+
+  // NSS lacks the ability to import an ECC private key without
+  // also importing the public key, so it is necessary to also
+  // store the public key.
+  vector<uint8> public_key;
+  if (!key_pair->ExportPublicKey(&public_key)) {
+    DVLOG(1) << "Can't export public key.";
+    return string();
+  }
+
+  // TODO(thaidn): determine how large encrypted private key can be
+  uint16 private_key_size = private_key.size();
+  const size_t result_size = sizeof(private_key_size) +
+                             private_key_size +
+                             public_key.size();
+  vector<char> result(result_size);
+  char* resultp = &result[0];
+  // Export the key string.
+  // The first two bytes are the private key's size in little endian.
+  private_key_size = base::ByteSwapToLE16(private_key_size);
+  memcpy(resultp, &private_key_size, sizeof(private_key_size));
+  resultp += sizeof(private_key_size);
+  memcpy(resultp, &private_key[0], private_key.size());
+  resultp += private_key.size();
+  memcpy(resultp, &public_key[0], public_key.size());
+
+  return string(&result[0], result_size);
+}
+
+KeyExchange* P256KeyExchange::NewKeyPair(QuicRandom* /*rand*/) const {
+  // TODO(agl): avoid the serialisation/deserialisation in this function.
+  const string private_value = NewPrivateKey();
+  return P256KeyExchange::New(private_value);
+}
+
+bool P256KeyExchange::CalculateSharedKey(const StringPiece& peer_public_value,
+                                         string* out_result) const {
+  if (peer_public_value.size() != kUncompressedP256PointBytes ||
+      peer_public_value[0] != kUncompressedECPointForm) {
+    DVLOG(1) << "Peer public value is invalid.";
+    return false;
+  }
+
+  DCHECK(key_pair_.get());
+  DCHECK(key_pair_->public_key());
+
+  SECKEYPublicKey peer_public_key;
+  memset(&peer_public_key, 0, sizeof(peer_public_key));
+
+  peer_public_key.keyType = ecKey;
+  // Both sides of a ECDH key exchange need to use the same EC params.
+  peer_public_key.u.ec.DEREncodedParams.len =
+      key_pair_->public_key()->u.ec.DEREncodedParams.len;
+  peer_public_key.u.ec.DEREncodedParams.data =
+      key_pair_->public_key()->u.ec.DEREncodedParams.data;
+
+  peer_public_key.u.ec.publicValue.type = siBuffer;
+  peer_public_key.u.ec.publicValue.data =
+      reinterpret_cast<uint8*>(const_cast<char*>(peer_public_value.data()));
+  peer_public_key.u.ec.publicValue.len = peer_public_value.size();
+
+  // The NSS function performing ECDH key exchange is PK11_PubDeriveWithKDF.
+  // As this function is used for SSL/TLS's ECDH key exchanges it has many
+  // arguments, most of which are not required in QUIC.
+  // Key derivation function CKD_NULL is used because the return value of
+  // |CalculateSharedKey| is the actual ECDH shared key, not any derived keys
+  // from it.
+  crypto::ScopedPK11SymKey premaster_secret(
+      PK11_PubDeriveWithKDF(
+          key_pair_->key(),
+          &peer_public_key,
+          PR_FALSE,
+          nullptr,
+          nullptr,
+          CKM_ECDH1_DERIVE, /* mechanism */
+          CKM_GENERIC_SECRET_KEY_GEN, /* target */
+          CKA_DERIVE,
+          0,
+          CKD_NULL, /* kdf */
+          nullptr,
+          nullptr));
+
+  if (!premaster_secret.get()) {
+    DVLOG(1) << "Can't derive ECDH shared key.";
+    return false;
+  }
+
+  if (PK11_ExtractKeyValue(premaster_secret.get()) != SECSuccess) {
+    DVLOG(1) << "Can't extract raw ECDH shared key.";
+    return false;
+  }
+
+  SECItem* key_data = PK11_GetKeyData(premaster_secret.get());
+  if (!key_data || !key_data->data || key_data->len != kP256FieldBytes) {
+    DVLOG(1) << "ECDH shared key is invalid.";
+    return false;
+  }
+
+  out_result->assign(reinterpret_cast<char*>(key_data->data), key_data->len);
+  return true;
+}
+
+StringPiece P256KeyExchange::public_value() const {
+  return StringPiece(reinterpret_cast<const char*>(public_key_),
+                     sizeof(public_key_));
+}
+
+QuicTag P256KeyExchange::tag() const { return kP256; }
+
+}  // namespace net
diff --git a/net/quic/crypto/p256_key_exchange_openssl.cc b/net/quic/crypto/p256_key_exchange_openssl.cc
new file mode 100644
index 0000000..f6d88b6
--- /dev/null
+++ b/net/quic/crypto/p256_key_exchange_openssl.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/p256_key_exchange.h"
+
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
+#include <openssl/evp.h>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+P256KeyExchange::P256KeyExchange(EC_KEY* private_key, const uint8* public_key)
+    : private_key_(private_key) {
+  memcpy(public_key_, public_key, sizeof(public_key_));
+}
+
+P256KeyExchange::~P256KeyExchange() {}
+
+// static
+P256KeyExchange* P256KeyExchange::New(StringPiece key) {
+  if (key.empty()) {
+    DVLOG(1) << "Private key is empty";
+    return nullptr;
+  }
+
+  const uint8* keyp = reinterpret_cast<const uint8*>(key.data());
+  crypto::ScopedEC_KEY private_key(d2i_ECPrivateKey(nullptr, &keyp,
+                                                    key.size()));
+  if (!private_key.get() || !EC_KEY_check_key(private_key.get())) {
+    DVLOG(1) << "Private key is invalid.";
+    return nullptr;
+  }
+
+  uint8 public_key[kUncompressedP256PointBytes];
+  if (EC_POINT_point2oct(EC_KEY_get0_group(private_key.get()),
+                         EC_KEY_get0_public_key(private_key.get()),
+                         POINT_CONVERSION_UNCOMPRESSED, public_key,
+                         sizeof(public_key), nullptr) != sizeof(public_key)) {
+    DVLOG(1) << "Can't get public key.";
+    return nullptr;
+  }
+
+  return new P256KeyExchange(private_key.release(), public_key);
+}
+
+// static
+string P256KeyExchange::NewPrivateKey() {
+  crypto::ScopedEC_KEY key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  if (!key.get() || !EC_KEY_generate_key(key.get())) {
+    DVLOG(1) << "Can't generate a new private key.";
+    return string();
+  }
+
+  int key_len = i2d_ECPrivateKey(key.get(), nullptr);
+  if (key_len <= 0) {
+    DVLOG(1) << "Can't convert private key to string";
+    return string();
+  }
+  scoped_ptr<uint8[]> private_key(new uint8[key_len]);
+  uint8* keyp = private_key.get();
+  if (!i2d_ECPrivateKey(key.get(), &keyp)) {
+    DVLOG(1) << "Can't convert private key to string.";
+    return string();
+  }
+  return string(reinterpret_cast<char*>(private_key.get()), key_len);
+}
+
+KeyExchange* P256KeyExchange::NewKeyPair(QuicRandom* /*rand*/) const {
+  // TODO(agl): avoid the serialisation/deserialisation in this function.
+  const string private_value = NewPrivateKey();
+  return P256KeyExchange::New(private_value);
+}
+
+bool P256KeyExchange::CalculateSharedKey(const StringPiece& peer_public_value,
+                                         string* out_result) const {
+  if (peer_public_value.size() != kUncompressedP256PointBytes) {
+    DVLOG(1) << "Peer public value is invalid";
+    return false;
+  }
+
+  crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free>::Type point(
+      EC_POINT_new(EC_KEY_get0_group(private_key_.get())));
+  if (!point.get() ||
+      !EC_POINT_oct2point( /* also test if point is on curve */
+          EC_KEY_get0_group(private_key_.get()),
+          point.get(),
+          reinterpret_cast<const uint8*>(peer_public_value.data()),
+          peer_public_value.size(), nullptr)) {
+    DVLOG(1) << "Can't convert peer public value to curve point.";
+    return false;
+  }
+
+  uint8 result[kP256FieldBytes];
+  if (ECDH_compute_key(result, sizeof(result), point.get(), private_key_.get(),
+                       nullptr) != sizeof(result)) {
+    DVLOG(1) << "Can't compute ECDH shared key.";
+    return false;
+  }
+
+  out_result->assign(reinterpret_cast<char*>(result), sizeof(result));
+  return true;
+}
+
+StringPiece P256KeyExchange::public_value() const {
+  return StringPiece(reinterpret_cast<const char*>(public_key_),
+                     sizeof(public_key_));
+}
+
+QuicTag P256KeyExchange::tag() const { return kP256; }
+
+}  // namespace net
diff --git a/net/quic/crypto/p256_key_exchange_test.cc b/net/quic/crypto/p256_key_exchange_test.cc
new file mode 100644
index 0000000..54c99a0
--- /dev/null
+++ b/net/quic/crypto/p256_key_exchange_test.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/p256_key_exchange.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+
+// SharedKey just tests that the basic key exchange identity holds: that both
+// parties end up with the same key.
+TEST(P256KeyExchange, SharedKey) {
+  for (int i = 0; i < 5; i++) {
+    string alice_private(P256KeyExchange::NewPrivateKey());
+    string bob_private(P256KeyExchange::NewPrivateKey());
+
+    ASSERT_FALSE(alice_private.empty());
+    ASSERT_FALSE(bob_private.empty());
+    ASSERT_NE(alice_private, bob_private);
+
+    scoped_ptr<P256KeyExchange> alice(P256KeyExchange::New(alice_private));
+    scoped_ptr<P256KeyExchange> bob(P256KeyExchange::New(bob_private));
+
+    ASSERT_TRUE(alice.get() != nullptr);
+    ASSERT_TRUE(bob.get() != nullptr);
+
+    const base::StringPiece alice_public(alice->public_value());
+    const base::StringPiece bob_public(bob->public_value());
+
+    std::string alice_shared, bob_shared;
+    ASSERT_TRUE(alice->CalculateSharedKey(bob_public, &alice_shared));
+    ASSERT_TRUE(bob->CalculateSharedKey(alice_public, &bob_shared));
+    ASSERT_EQ(alice_shared, bob_shared);
+  }
+}
+
+}  // namespace test
+}  // namespace net
+
diff --git a/net/quic/crypto/proof_source.h b/net/quic/crypto/proof_source.h
new file mode 100644
index 0000000..4482dd9
--- /dev/null
+++ b/net/quic/crypto/proof_source.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_PROOF_SOURCE_H_
+#define NET_QUIC_CRYPTO_PROOF_SOURCE_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// ProofSource is an interface by which a QUIC server can obtain certificate
+// chains and signatures that prove its identity.
+class NET_EXPORT_PRIVATE ProofSource {
+ public:
+  virtual ~ProofSource() {}
+
+  // GetProof finds a certificate chain for |hostname|, sets |out_certs| to
+  // point to it (in leaf-first order), calculates a signature of
+  // |server_config| using that chain and puts the result in |out_signature|.
+  //
+  // The signature uses SHA-256 as the hash function and PSS padding when the
+  // key is RSA.
+  //
+  // The signature uses SHA-256 as the hash function when the key is ECDSA.
+  //
+  // If |ecdsa_ok| is true, the signature may use an ECDSA key. Otherwise, the
+  // signature must use an RSA key.
+  //
+  // |out_certs| is a pointer to a pointer, not a pointer to an array.
+  //
+  // The number of certificate chains is expected to be small and fixed thus
+  // the ProofSource retains ownership of the contents of |out_certs|. The
+  // expectation is that they will be cached forever.
+  //
+  // The signature values should be cached because |server_config| will be
+  // somewhat static. However, since they aren't bounded, the ProofSource may
+  // wish to evicit entries from that cache, thus the caller takes ownership of
+  // |*out_signature|.
+  //
+  // |hostname| may be empty to signify that a default certificate should be
+  // used.
+  //
+  // This function may be called concurrently.
+  virtual bool GetProof(const std::string& hostname,
+                        const std::string& server_config,
+                        bool ecdsa_ok,
+                        const std::vector<std::string>** out_certs,
+                        std::string* out_signature) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_PROOF_SOURCE_H_
diff --git a/net/quic/crypto/proof_source_chromium.cc b/net/quic/crypto/proof_source_chromium.cc
new file mode 100644
index 0000000..7522631
--- /dev/null
+++ b/net/quic/crypto/proof_source_chromium.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/proof_source_chromium.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+
+ProofSourceChromium::ProofSourceChromium() {
+}
+
+bool ProofSourceChromium::GetProof(const string& hostname,
+                                   const string& server_config,
+                                   bool ecdsa_ok,
+                                   const vector<string>** out_certs,
+                                   string* out_signature) {
+  return false;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/proof_source_chromium.h b/net/quic/crypto/proof_source_chromium.h
new file mode 100644
index 0000000..70ab92d
--- /dev/null
+++ b/net/quic/crypto/proof_source_chromium.h
@@ -0,0 +1,38 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
+#define NET_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/proof_source.h"
+
+namespace net {
+
+// ProofSourceChromium implements the QUIC ProofSource interface.
+// TODO(rtenneti): implement details of this class.
+class NET_EXPORT_PRIVATE ProofSourceChromium : public ProofSource {
+ public:
+  ProofSourceChromium();
+  virtual ~ProofSourceChromium() {}
+
+  // ProofSource interface
+  virtual bool GetProof(const std::string& hostname,
+                        const std::string& server_config,
+                        bool ecdsa_ok,
+                        const std::vector<std::string>** out_certs,
+                        std::string* out_signature) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProofSourceChromium);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
diff --git a/net/quic/crypto/proof_test.cc b/net/quic/crypto/proof_test.cc
new file mode 100644
index 0000000..031cba2
--- /dev/null
+++ b/net/quic/crypto/proof_test.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_source.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+// TestProofVerifierCallback is a simple callback for a ProofVerifier that
+// signals a TestCompletionCallback when called and stores the results from the
+// ProofVerifier in pointers passed to the constructor.
+class TestProofVerifierCallback : public ProofVerifierCallback {
+ public:
+  TestProofVerifierCallback(TestCompletionCallback* comp_callback,
+                            bool* ok,
+                            string* error_details)
+      : comp_callback_(comp_callback),
+        ok_(ok),
+        error_details_(error_details) {}
+
+  virtual void Run(bool ok,
+                   const string& error_details,
+                   scoped_ptr<ProofVerifyDetails>* details) OVERRIDE {
+    *ok_ = ok;
+    *error_details_ = error_details;
+
+    comp_callback_->callback().Run(0);
+  }
+
+ private:
+  TestCompletionCallback* const comp_callback_;
+  bool* const ok_;
+  string* const error_details_;
+};
+
+// RunVerification runs |verifier->VerifyProof| and asserts that the result
+// matches |expected_ok|.
+void RunVerification(ProofVerifier* verifier,
+                     const string& hostname,
+                     const string& server_config,
+                     const vector<string>& certs,
+                     const string& proof,
+                     bool expected_ok) {
+  scoped_ptr<ProofVerifyDetails> details;
+  TestCompletionCallback comp_callback;
+  bool ok;
+  string error_details;
+  scoped_ptr<ProofVerifyContext> verify_context(
+      CryptoTestUtils::ProofVerifyContextForTesting());
+  TestProofVerifierCallback* callback =
+      new TestProofVerifierCallback(&comp_callback, &ok, &error_details);
+
+  QuicAsyncStatus status = verifier->VerifyProof(
+      hostname, server_config, certs, proof, verify_context.get(),
+      &error_details, &details, callback);
+
+  switch (status) {
+    case QUIC_FAILURE:
+      delete callback;
+      ASSERT_FALSE(expected_ok);
+      ASSERT_NE("", error_details);
+      return;
+    case QUIC_SUCCESS:
+      delete callback;
+      ASSERT_TRUE(expected_ok);
+      ASSERT_EQ("", error_details);
+      return;
+    case QUIC_PENDING:
+      comp_callback.WaitForResult();
+      ASSERT_EQ(expected_ok, ok);
+      break;
+  }
+}
+
+// Reads the certificate named "quic_" + |file_name| in the test data directory.
+// The certificate must be PEM encoded. Returns the DER-encoded certificate.
+string LoadTestCert(const string& file_name) {
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> cert =
+      ImportCertFromFile(certs_dir, "quic_" + file_name);
+  CHECK_NE(static_cast<X509Certificate*>(nullptr), cert.get());
+
+  string der_bytes;
+  CHECK(X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_bytes));
+  return der_bytes;
+}
+
+}  // namespace
+
+// TODO(rtenneti): Enable testing of ProofVerifier.
+TEST(ProofTest, DISABLED_Verify) {
+  scoped_ptr<ProofSource> source(CryptoTestUtils::ProofSourceForTesting());
+  scoped_ptr<ProofVerifier> verifier(
+      CryptoTestUtils::ProofVerifierForTesting());
+
+  const string server_config = "server config bytes";
+  const string hostname = "test.example.com";
+  const vector<string>* certs;
+  const vector<string>* first_certs;
+  string error_details, signature, first_signature;
+
+  ASSERT_TRUE(source->GetProof(hostname, server_config, false /* no ECDSA */,
+                               &first_certs, &first_signature));
+  ASSERT_TRUE(source->GetProof(hostname, server_config, false /* no ECDSA */,
+                               &certs, &signature));
+
+  // Check that the proof source is caching correctly:
+  ASSERT_EQ(first_certs, certs);
+  ASSERT_EQ(signature, first_signature);
+
+  RunVerification(
+      verifier.get(), hostname, server_config, *certs, signature, true);
+
+  RunVerification(
+      verifier.get(), "foo.com", server_config, *certs, signature, false);
+
+  RunVerification(
+      verifier.get(), server_config.substr(1, string::npos), server_config,
+      *certs, signature, false);
+
+  const string corrupt_signature = "1" + signature;
+  RunVerification(
+      verifier.get(), hostname, server_config, *certs, corrupt_signature,
+      false);
+
+  vector<string> wrong_certs;
+  for (size_t i = 1; i < certs->size(); i++) {
+    wrong_certs.push_back((*certs)[i]);
+  }
+  RunVerification(
+      verifier.get(), "foo.com", server_config, wrong_certs, corrupt_signature,
+      false);
+}
+
+// A known answer test that allows us to test ProofVerifier without a working
+// ProofSource.
+TEST(ProofTest, VerifyRSAKnownAnswerTest) {
+  // These sample signatures were generated by running the Proof.Verify test
+  // and dumping the bytes of the |signature| output of ProofSource::GetProof().
+  static const unsigned char signature_data_0[] = {
+    0x31, 0xd5, 0xfb, 0x40, 0x30, 0x75, 0xd2, 0x7d, 0x61, 0xf9, 0xd7, 0x54,
+    0x30, 0x06, 0xaf, 0x54, 0x0d, 0xb0, 0x0a, 0xda, 0x63, 0xca, 0x7e, 0x9e,
+    0xce, 0xba, 0x10, 0x05, 0x1b, 0xa6, 0x7f, 0xef, 0x2b, 0xa3, 0xff, 0x3c,
+    0xbb, 0x9a, 0xe4, 0xbf, 0xb8, 0x0c, 0xc1, 0xbd, 0xed, 0xc2, 0x90, 0x68,
+    0xeb, 0x45, 0x48, 0xea, 0x3c, 0x95, 0xf8, 0xa2, 0xb9, 0xe7, 0x62, 0x29,
+    0x00, 0xc3, 0x18, 0xb4, 0x16, 0x6f, 0x5e, 0xb0, 0xc1, 0x26, 0xc0, 0x4b,
+    0x84, 0xf5, 0x97, 0xfc, 0x17, 0xf9, 0x1c, 0x43, 0xb8, 0xf2, 0x3f, 0x38,
+    0x32, 0xad, 0x36, 0x52, 0x2c, 0x26, 0x92, 0x7a, 0xea, 0x2c, 0xa2, 0xf4,
+    0x28, 0x2f, 0x19, 0x4d, 0x1f, 0x11, 0x46, 0x82, 0xd0, 0xc4, 0x86, 0x56,
+    0x5c, 0x97, 0x9e, 0xc6, 0x37, 0x8e, 0xaf, 0x9d, 0x69, 0xe9, 0x4f, 0x5a,
+    0x6d, 0x70, 0x75, 0xc7, 0x41, 0x95, 0x68, 0x53, 0x94, 0xca, 0x31, 0x63,
+    0x61, 0x9f, 0xb8, 0x8c, 0x3b, 0x75, 0x36, 0x8b, 0x69, 0xa2, 0x35, 0xc0,
+    0x4b, 0x77, 0x55, 0x08, 0xc2, 0xb4, 0x56, 0xd2, 0x81, 0xce, 0x9e, 0x25,
+    0xdb, 0x50, 0x74, 0xb3, 0x8a, 0xd9, 0x20, 0x42, 0x3f, 0x85, 0x2d, 0xaa,
+    0xfd, 0x66, 0xfa, 0xd6, 0x95, 0x55, 0x6b, 0x63, 0x63, 0x04, 0xf8, 0x6c,
+    0x3e, 0x08, 0x22, 0x39, 0xb9, 0x9a, 0xe0, 0xd7, 0x01, 0xff, 0xeb, 0x8a,
+    0xb9, 0xe2, 0x34, 0xa5, 0xa0, 0x51, 0xe9, 0xbe, 0x15, 0x12, 0xbf, 0xbe,
+    0x64, 0x3d, 0x3f, 0x98, 0xce, 0xc1, 0xa6, 0x33, 0x32, 0xd3, 0x5c, 0xa8,
+    0x39, 0x93, 0xdc, 0x1c, 0xb9, 0xab, 0x3c, 0x80, 0x62, 0xb3, 0x76, 0x21,
+    0xdf, 0x47, 0x1e, 0xa9, 0x0e, 0x5e, 0x8a, 0xbe, 0x66, 0x5b, 0x7c, 0x21,
+    0xfa, 0x78, 0x2d, 0xd1, 0x1d, 0x5c, 0x35, 0x8a, 0x34, 0xb2, 0x1a, 0xc2,
+    0xc4, 0x4b, 0x53, 0x54,
+  };
+  static const unsigned char signature_data_1[] = {
+    0x01, 0x7b, 0x52, 0x35, 0xe3, 0x51, 0xdd, 0xf1, 0x67, 0x8d, 0x31, 0x5e,
+    0xa3, 0x75, 0x1f, 0x68, 0x6c, 0xdd, 0x41, 0x7a, 0x18, 0x25, 0xe0, 0x12,
+    0x6e, 0x84, 0x46, 0x5e, 0xb2, 0x98, 0xd7, 0x84, 0xe1, 0x62, 0xe0, 0xc1,
+    0xc4, 0xd7, 0x4f, 0x4f, 0x80, 0xc1, 0x92, 0xd6, 0x02, 0xaf, 0xca, 0x28,
+    0x9f, 0xe0, 0xf3, 0x74, 0xd7, 0xf1, 0x44, 0x67, 0x59, 0x27, 0xc8, 0xc2,
+    0x8b, 0xd4, 0xe5, 0x4a, 0x07, 0xfd, 0x00, 0xd6, 0x8a, 0xbf, 0x8b, 0xcd,
+    0x6a, 0xe0, 0x1d, 0xf6, 0x4b, 0x68, 0x0f, 0xcf, 0xb9, 0xd0, 0xa1, 0xbc,
+    0x2e, 0xcf, 0x7c, 0x03, 0x47, 0x11, 0xe4, 0x4c, 0xbc, 0x1b, 0x6b, 0xa5,
+    0x2a, 0x82, 0x86, 0xa4, 0x7f, 0x1d, 0x85, 0x64, 0x21, 0x10, 0xd2, 0xb2,
+    0xa0, 0x31, 0xa2, 0x78, 0xe6, 0xf2, 0xea, 0x96, 0x38, 0x8c, 0x9a, 0xe1,
+    0x01, 0xab, 0x8e, 0x95, 0x66, 0xc8, 0xe5, 0xcc, 0x80, 0xa3, 0xbd, 0x16,
+    0xa7, 0x79, 0x19, 0x39, 0x61, 0x3d, 0xff, 0x37, 0xca, 0x9f, 0x97, 0x05,
+    0xc7, 0xcb, 0xf0, 0xea, 0xaf, 0x64, 0x07, 0xc0, 0xed, 0x2a, 0x98, 0xa4,
+    0xaf, 0x04, 0x6f, 0xf2, 0xc9, 0xb2, 0x73, 0x9a, 0x56, 0x85, 0x43, 0x64,
+    0x5f, 0xaa, 0xb7, 0xff, 0x31, 0x4c, 0x2e, 0x6c, 0x17, 0xcf, 0xe5, 0xbe,
+    0x7f, 0x7e, 0xad, 0xf5, 0x6f, 0x84, 0x50, 0x20, 0x29, 0xb3, 0x57, 0xe7,
+    0xb1, 0xdc, 0x2c, 0x95, 0x48, 0xfe, 0xb0, 0xc1, 0x92, 0xda, 0xc5, 0x58,
+    0x95, 0xb0, 0x1a, 0x3a, 0x05, 0x71, 0x3c, 0x6d, 0x20, 0x01, 0x4c, 0xa9,
+    0xe4, 0x38, 0x08, 0x65, 0xb4, 0xbd, 0x86, 0x76, 0xbd, 0xad, 0x25, 0x06,
+    0x74, 0x0b, 0xca, 0x95, 0x27, 0x0c, 0x13, 0x08, 0x7e, 0x30, 0xcf, 0xf6,
+    0xb5, 0xc1, 0x2a, 0x08, 0xfc, 0x4b, 0xc6, 0xb5, 0x2f, 0x23, 0x27, 0x32,
+    0x89, 0xdb, 0x0e, 0x4a,
+  };
+  static const unsigned char signature_data_2[] = {
+    0x6d, 0x7d, 0x22, 0x8c, 0x85, 0xc4, 0x8a, 0x80, 0x05, 0xe4, 0x3c, 0xaf,
+    0x10, 0x3b, 0xe3, 0x51, 0xb1, 0x86, 0x52, 0x63, 0xb6, 0x17, 0x33, 0xbd,
+    0x1b, 0x1e, 0xc4, 0x50, 0x10, 0xfc, 0xcc, 0xea, 0x6b, 0x11, 0xeb, 0x6d,
+    0x5e, 0x00, 0xe7, 0xf3, 0x67, 0x99, 0x74, 0x53, 0x12, 0x8f, 0xe4, 0x3e,
+    0x20, 0x17, 0x8e, 0x83, 0xe6, 0xdc, 0x83, 0x91, 0x0e, 0xf3, 0x69, 0x22,
+    0x95, 0x14, 0xdf, 0xc1, 0xda, 0xb5, 0xdb, 0x6a, 0x1a, 0xb4, 0x4f, 0x26,
+    0xd0, 0x32, 0x1d, 0x73, 0x95, 0x1f, 0x39, 0x1d, 0x00, 0xcb, 0xc3, 0x92,
+    0x49, 0x53, 0xcb, 0x5c, 0x36, 0x70, 0x19, 0xd9, 0x64, 0x36, 0xda, 0xfb,
+    0x20, 0xe5, 0x47, 0xd9, 0x08, 0xc6, 0x5a, 0x9e, 0x87, 0x1a, 0xdb, 0x11,
+    0x7b, 0x17, 0xfc, 0x53, 0x7b, 0xc1, 0xa0, 0xc0, 0x33, 0xcf, 0x96, 0xba,
+    0x03, 0x79, 0x8e, 0xc6, 0x05, 0xd2, 0xb7, 0xa2, 0xe2, 0xc1, 0x67, 0xb7,
+    0x6a, 0xeb, 0xb1, 0x40, 0xbb, 0x7d, 0x57, 0xcb, 0xc2, 0x60, 0x9f, 0xf1,
+    0x72, 0xe5, 0xad, 0xce, 0x95, 0x45, 0x7c, 0xbc, 0x75, 0x81, 0x45, 0x19,
+    0xe1, 0xa7, 0x2f, 0x05, 0x52, 0xeb, 0xed, 0xdd, 0x19, 0xd9, 0x1a, 0xc9,
+    0x5a, 0x06, 0x8e, 0x29, 0x54, 0xb5, 0x4f, 0x80, 0xaa, 0x36, 0x36, 0xc0,
+    0xff, 0x64, 0xac, 0xe8, 0x0f, 0x99, 0x35, 0x5e, 0xc6, 0x72, 0x1f, 0x8c,
+    0xc4, 0x2b, 0x7d, 0xc1, 0xfb, 0xf0, 0x12, 0x61, 0xb1, 0x18, 0x65, 0xdd,
+    0xc2, 0x38, 0x92, 0xba, 0x84, 0xf8, 0xc8, 0x5e, 0x17, 0x63, 0xe0, 0x9c,
+    0x2c, 0xe6, 0x70, 0x71, 0xdc, 0xe5, 0xc1, 0xea, 0xb3, 0x9a, 0xb6, 0x91,
+    0xdc, 0xc5, 0x56, 0x84, 0x8a, 0x31, 0x31, 0x23, 0x61, 0x94, 0x7e, 0x01,
+    0x22, 0x49, 0xf3, 0xcb, 0x0e, 0x31, 0x03, 0x04, 0x1b, 0x14, 0x43, 0x7c,
+    0xad, 0x42, 0xe5, 0x55,
+  };
+
+  scoped_ptr<ProofVerifier> verifier(
+      CryptoTestUtils::ProofVerifierForTesting());
+
+  const string server_config = "server config bytes";
+  const string hostname = "test.example.com";
+
+  vector<string> certs(2);
+  certs[0] = LoadTestCert("test.example.com.crt");
+  certs[1] = LoadTestCert("intermediate.crt");
+
+  // Signatures are nondeterministic, so we test multiple signatures on the
+  // same server_config.
+  vector<string> signatures(3);
+  signatures[0].assign(reinterpret_cast<const char*>(signature_data_0),
+                       sizeof(signature_data_0));
+  signatures[1].assign(reinterpret_cast<const char*>(signature_data_1),
+                       sizeof(signature_data_1));
+  signatures[2].assign(reinterpret_cast<const char*>(signature_data_2),
+                       sizeof(signature_data_2));
+
+  for (size_t i = 0; i < signatures.size(); i++) {
+    const string& signature = signatures[i];
+
+    RunVerification(
+        verifier.get(), hostname, server_config, certs, signature, true);
+    RunVerification(
+        verifier.get(), "foo.com", server_config, certs, signature, false);
+    RunVerification(
+        verifier.get(), hostname, server_config.substr(1, string::npos),
+        certs, signature, false);
+
+    const string corrupt_signature = "1" + signature;
+    RunVerification(
+        verifier.get(), hostname, server_config, certs, corrupt_signature,
+        false);
+
+    vector<string> wrong_certs;
+    for (size_t i = 1; i < certs.size(); i++) {
+      wrong_certs.push_back(certs[i]);
+    }
+    RunVerification(verifier.get(), hostname, server_config, wrong_certs,
+                    signature, false);
+  }
+}
+
+// A known answer test that allows us to test ProofVerifier without a working
+// ProofSource.
+TEST(ProofTest, VerifyECDSAKnownAnswerTest) {
+  // Disable this test on platforms that do not support ECDSA certificates.
+#if defined(OS_WIN)
+  if (base::win::GetVersion() < base::win::VERSION_VISTA)
+    return;
+#endif
+
+  // These sample signatures were generated by running the Proof.Verify test
+  // (modified to use ECDSA for signing proofs) and dumping the bytes of the
+  // |signature| output of ProofSource::GetProof().
+  static const unsigned char signature_data_0[] = {
+    0x30, 0x45, 0x02, 0x21, 0x00, 0x89, 0xc4, 0x7d, 0x08, 0xd1, 0x49, 0x19,
+    0x6c, 0xd1, 0x7c, 0xb9, 0x25, 0xe0, 0xe3, 0xbd, 0x6a, 0x5c, 0xd7, 0xaa,
+    0x0c, 0xdc, 0x4f, 0x8e, 0xeb, 0xde, 0xbf, 0x32, 0xf8, 0xd1, 0x84, 0x95,
+    0x97, 0x02, 0x20, 0x29, 0x3d, 0x49, 0x22, 0x73, 0xed, 0x8b, 0xde, 0x3d,
+    0xc2, 0xa4, 0x20, 0xcc, 0xe7, 0xc8, 0x2a, 0x85, 0x20, 0x9b, 0x5b, 0xda,
+    0xcd, 0x58, 0x23, 0xbe, 0x89, 0x73, 0x31, 0x87, 0x51, 0xd1, 0x01,
+  };
+  static const unsigned char signature_data_1[] = {
+    0x30, 0x46, 0x02, 0x21, 0x00, 0xec, 0xdf, 0x69, 0xc8, 0x24, 0x59, 0x93,
+    0xda, 0x49, 0xee, 0x37, 0x28, 0xaf, 0xeb, 0x0e, 0x2f, 0x80, 0x17, 0x4b,
+    0x3b, 0xf6, 0x54, 0xcd, 0x3b, 0x86, 0xc5, 0x98, 0x0d, 0xff, 0xc6, 0xb1,
+    0xe7, 0x02, 0x21, 0x00, 0xe1, 0x36, 0x8c, 0xc0, 0xf4, 0x50, 0x5f, 0xba,
+    0xfb, 0xe2, 0xff, 0x1d, 0x5d, 0x64, 0xe4, 0x07, 0xbb, 0x5a, 0x4b, 0x19,
+    0xb6, 0x39, 0x7a, 0xc4, 0x12, 0xc6, 0xe5, 0x42, 0xc8, 0x78, 0x33, 0xcd,
+  };
+  static const unsigned char signature_data_2[] = {
+    0x30, 0x45, 0x02, 0x20, 0x09, 0x51, 0xe9, 0xde, 0xdb, 0x01, 0xfd, 0xb4,
+    0xd8, 0x20, 0xbb, 0xad, 0x41, 0xe3, 0xaa, 0xe7, 0xa3, 0xc3, 0x32, 0x10,
+    0x9d, 0xfa, 0x37, 0xce, 0x17, 0xd1, 0x29, 0xf9, 0xd4, 0x1d, 0x0d, 0x19,
+    0x02, 0x21, 0x00, 0xc6, 0x20, 0xd4, 0x28, 0xf9, 0x70, 0xb5, 0xb4, 0xff,
+    0x4a, 0x35, 0xba, 0xa0, 0xf2, 0x8e, 0x00, 0xf7, 0xcb, 0x43, 0xaf, 0x2d,
+    0x1f, 0xce, 0x92, 0x05, 0xca, 0x29, 0xfe, 0xd2, 0x8f, 0xd9, 0x31,
+  };
+
+  scoped_ptr<ProofVerifier> verifier(
+      CryptoTestUtils::ProofVerifierForTesting());
+
+  const string server_config = "server config bytes";
+  const string hostname = "test.example.com";
+
+  vector<string> certs(2);
+  certs[0] = LoadTestCert("test_ecc.example.com.crt");
+  certs[1] = LoadTestCert("intermediate.crt");
+
+  // Signatures are nondeterministic, so we test multiple signatures on the
+  // same server_config.
+  vector<string> signatures(3);
+  signatures[0].assign(reinterpret_cast<const char*>(signature_data_0),
+                       sizeof(signature_data_0));
+  signatures[1].assign(reinterpret_cast<const char*>(signature_data_1),
+                       sizeof(signature_data_1));
+  signatures[2].assign(reinterpret_cast<const char*>(signature_data_2),
+                       sizeof(signature_data_2));
+
+  for (size_t i = 0; i < signatures.size(); i++) {
+    const string& signature = signatures[i];
+
+    RunVerification(
+        verifier.get(), hostname, server_config, certs, signature, true);
+    RunVerification(
+        verifier.get(), "foo.com", server_config, certs, signature, false);
+    RunVerification(
+        verifier.get(), hostname, server_config.substr(1, string::npos),
+        certs, signature, false);
+
+    // An ECDSA signature is DER-encoded. Corrupt the last byte so that the
+    // signature can still be DER-decoded correctly.
+    string corrupt_signature = signature;
+    corrupt_signature[corrupt_signature.size() - 1] += 1;
+    RunVerification(
+        verifier.get(), hostname, server_config, certs, corrupt_signature,
+        false);
+
+    // Prepending a "1" makes the DER invalid.
+    const string bad_der_signature1 = "1" + signature;
+    RunVerification(
+        verifier.get(), hostname, server_config, certs, bad_der_signature1,
+        false);
+
+    vector<string> wrong_certs;
+    for (size_t i = 1; i < certs.size(); i++) {
+      wrong_certs.push_back(certs[i]);
+    }
+    RunVerification(
+        verifier.get(), hostname, server_config, wrong_certs, signature,
+        false);
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/proof_verifier.h b/net/quic/crypto/proof_verifier.h
new file mode 100644
index 0000000..87339da
--- /dev/null
+++ b/net/quic/crypto/proof_verifier.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_PROOF_VERIFIER_H_
+#define NET_QUIC_CRYPTO_PROOF_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_types.h"
+
+namespace net {
+
+// ProofVerifyDetails is an abstract class that acts as a container for any
+// implementation specific details that a ProofVerifier wishes to return. These
+// details are saved in the CachedState for the origin in question.
+class NET_EXPORT_PRIVATE ProofVerifyDetails {
+ public:
+  virtual ~ProofVerifyDetails() {}
+
+  // Returns an new ProofVerifyDetails object with the same contents
+  // as this one.
+  virtual ProofVerifyDetails* Clone() const = 0;
+};
+
+// ProofVerifyContext is an abstract class that acts as a container for any
+// implementation specific context that a ProofVerifier needs.
+class NET_EXPORT_PRIVATE ProofVerifyContext {
+ public:
+  virtual ~ProofVerifyContext() {}
+};
+
+// ProofVerifierCallback provides a generic mechanism for a ProofVerifier to
+// call back after an asynchronous verification.
+class NET_EXPORT_PRIVATE ProofVerifierCallback {
+ public:
+  virtual ~ProofVerifierCallback() {}
+
+  // Run is called on the original thread to mark the completion of an
+  // asynchonous verification. If |ok| is true then the certificate is valid
+  // and |error_details| is unused. Otherwise, |error_details| contains a
+  // description of the error. |details| contains implementation-specific
+  // details of the verification. |Run| may take ownership of |details| by
+  // calling |release| on it.
+  virtual void Run(bool ok,
+                   const std::string& error_details,
+                   scoped_ptr<ProofVerifyDetails>* details) = 0;
+};
+
+// A ProofVerifier checks the signature on a server config, and the certificate
+// chain that backs the public key.
+class NET_EXPORT_PRIVATE ProofVerifier {
+ public:
+  virtual ~ProofVerifier() {}
+
+  // VerifyProof checks that |signature| is a valid signature of
+  // |server_config| by the public key in the leaf certificate of |certs|, and
+  // that |certs| is a valid chain for |hostname|. On success, it returns
+  // QUIC_SUCCESS. On failure, it returns QUIC_FAILURE and sets |*error_details|
+  // to a description of the problem. In either case it may set |*details|,
+  // which the caller takes ownership of.
+  //
+  // |context| specifies an implementation specific struct (which may be nullptr
+  // for some implementations) that provides useful information for the
+  // verifier, e.g. logging handles.
+  //
+  // This function may also return QUIC_PENDING, in which case the ProofVerifier
+  // will call back, on the original thread, via |callback| when complete.
+  // In this case, the ProofVerifier will take ownership of |callback|.
+  //
+  // The signature uses SHA-256 as the hash function and PSS padding in the
+  // case of RSA.
+  virtual QuicAsyncStatus VerifyProof(const std::string& hostname,
+                                      const std::string& server_config,
+                                      const std::vector<std::string>& certs,
+                                      const std::string& signature,
+                                      const ProofVerifyContext* context,
+                                      std::string* error_details,
+                                      scoped_ptr<ProofVerifyDetails>* details,
+                                      ProofVerifierCallback* callback) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_PROOF_VERIFIER_H_
diff --git a/net/quic/crypto/proof_verifier_chromium.cc b/net/quic/crypto/proof_verifier_chromium.cc
new file mode 100644
index 0000000..775e374
--- /dev/null
+++ b/net/quic/crypto/proof_verifier_chromium.cc
@@ -0,0 +1,385 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/proof_verifier_chromium.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/signature_verifier.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/single_request_cert_verifier.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/ssl/ssl_config_service.h"
+
+using base::StringPiece;
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace net {
+
+ProofVerifyDetails* ProofVerifyDetailsChromium::Clone() const {
+  ProofVerifyDetailsChromium* other = new ProofVerifyDetailsChromium;
+  other->cert_verify_result = cert_verify_result;
+  return other;
+}
+
+// A Job handles the verification of a single proof.  It is owned by the
+// ProofVerifier. If the verification can not complete synchronously, it
+// will notify the ProofVerifier upon completion.
+class ProofVerifierChromium::Job {
+ public:
+  Job(ProofVerifierChromium* proof_verifier,
+      CertVerifier* cert_verifier,
+      TransportSecurityState* transport_security_state,
+      const BoundNetLog& net_log);
+
+  // Starts the proof verification.  If |QUIC_PENDING| is returned, then
+  // |callback| will be invoked asynchronously when the verification completes.
+  QuicAsyncStatus VerifyProof(const std::string& hostname,
+                              const std::string& server_config,
+                              const std::vector<std::string>& certs,
+                              const std::string& signature,
+                              std::string* error_details,
+                              scoped_ptr<ProofVerifyDetails>* verify_details,
+                              ProofVerifierCallback* callback);
+
+ private:
+  enum State {
+    STATE_NONE,
+    STATE_VERIFY_CERT,
+    STATE_VERIFY_CERT_COMPLETE,
+  };
+
+  int DoLoop(int last_io_result);
+  void OnIOComplete(int result);
+  int DoVerifyCert(int result);
+  int DoVerifyCertComplete(int result);
+
+  bool VerifySignature(const std::string& signed_data,
+                       const std::string& signature,
+                       const std::string& cert);
+
+  // Proof verifier to notify when this jobs completes.
+  ProofVerifierChromium* proof_verifier_;
+
+  // The underlying verifier used for verifying certificates.
+  scoped_ptr<SingleRequestCertVerifier> verifier_;
+
+  TransportSecurityState* transport_security_state_;
+
+  // |hostname| specifies the hostname for which |certs| is a valid chain.
+  std::string hostname_;
+
+  scoped_ptr<ProofVerifierCallback> callback_;
+  scoped_ptr<ProofVerifyDetailsChromium> verify_details_;
+  std::string error_details_;
+
+  // X509Certificate from a chain of DER encoded certificates.
+  scoped_refptr<X509Certificate> cert_;
+
+  State next_state_;
+
+  BoundNetLog net_log_;
+
+  DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+ProofVerifierChromium::Job::Job(
+    ProofVerifierChromium* proof_verifier,
+    CertVerifier* cert_verifier,
+    TransportSecurityState* transport_security_state,
+    const BoundNetLog& net_log)
+    : proof_verifier_(proof_verifier),
+      verifier_(new SingleRequestCertVerifier(cert_verifier)),
+      transport_security_state_(transport_security_state),
+      next_state_(STATE_NONE),
+      net_log_(net_log) {
+}
+
+QuicAsyncStatus ProofVerifierChromium::Job::VerifyProof(
+    const string& hostname,
+    const string& server_config,
+    const vector<string>& certs,
+    const string& signature,
+    std::string* error_details,
+    scoped_ptr<ProofVerifyDetails>* verify_details,
+    ProofVerifierCallback* callback) {
+  DCHECK(error_details);
+  DCHECK(verify_details);
+  DCHECK(callback);
+
+  error_details->clear();
+
+  if (STATE_NONE != next_state_) {
+    *error_details = "Certificate is already set and VerifyProof has begun";
+    DLOG(DFATAL) << *error_details;
+    return QUIC_FAILURE;
+  }
+
+  verify_details_.reset(new ProofVerifyDetailsChromium);
+
+  if (certs.empty()) {
+    *error_details = "Failed to create certificate chain. Certs are empty.";
+    DLOG(WARNING) << *error_details;
+    verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+    *verify_details = verify_details_.Pass();
+    return QUIC_FAILURE;
+  }
+
+  // Convert certs to X509Certificate.
+  vector<StringPiece> cert_pieces(certs.size());
+  for (unsigned i = 0; i < certs.size(); i++) {
+    cert_pieces[i] = base::StringPiece(certs[i]);
+  }
+  cert_ = X509Certificate::CreateFromDERCertChain(cert_pieces);
+  if (!cert_.get()) {
+    *error_details = "Failed to create certificate chain";
+    DLOG(WARNING) << *error_details;
+    verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+    *verify_details = verify_details_.Pass();
+    return QUIC_FAILURE;
+  }
+
+  // We call VerifySignature first to avoid copying of server_config and
+  // signature.
+  if (!VerifySignature(server_config, signature, certs[0])) {
+    *error_details = "Failed to verify signature of server config";
+    DLOG(WARNING) << *error_details;
+    verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+    *verify_details = verify_details_.Pass();
+    return QUIC_FAILURE;
+  }
+
+  hostname_ = hostname;
+
+  next_state_ = STATE_VERIFY_CERT;
+  switch (DoLoop(OK)) {
+    case OK:
+      *verify_details = verify_details_.Pass();
+      return QUIC_SUCCESS;
+    case ERR_IO_PENDING:
+      callback_.reset(callback);
+      return QUIC_PENDING;
+    default:
+      *error_details = error_details_;
+      *verify_details = verify_details_.Pass();
+      return QUIC_FAILURE;
+  }
+}
+
+int ProofVerifierChromium::Job::DoLoop(int last_result) {
+  int rv = last_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_VERIFY_CERT:
+        DCHECK(rv == OK);
+        rv = DoVerifyCert(rv);
+        break;
+      case STATE_VERIFY_CERT_COMPLETE:
+        rv = DoVerifyCertComplete(rv);
+        break;
+      case STATE_NONE:
+      default:
+        rv = ERR_UNEXPECTED;
+        LOG(DFATAL) << "unexpected state " << state;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+  return rv;
+}
+
+void ProofVerifierChromium::Job::OnIOComplete(int result) {
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    scoped_ptr<ProofVerifierCallback> callback(callback_.Pass());
+    // Callback expects ProofVerifyDetails not ProofVerifyDetailsChromium.
+    scoped_ptr<ProofVerifyDetails> verify_details(verify_details_.Pass());
+    callback->Run(rv == OK, error_details_, &verify_details);
+    // Will delete |this|.
+    proof_verifier_->OnJobComplete(this);
+  }
+}
+
+int ProofVerifierChromium::Job::DoVerifyCert(int result) {
+  next_state_ = STATE_VERIFY_CERT_COMPLETE;
+
+  int flags = 0;
+  return verifier_->Verify(
+      cert_.get(),
+      hostname_,
+      flags,
+      SSLConfigService::GetCRLSet().get(),
+      &verify_details_->cert_verify_result,
+      base::Bind(&ProofVerifierChromium::Job::OnIOComplete,
+                 base::Unretained(this)),
+      net_log_);
+}
+
+int ProofVerifierChromium::Job::DoVerifyCertComplete(int result) {
+  verifier_.reset();
+
+  const CertVerifyResult& cert_verify_result =
+      verify_details_->cert_verify_result;
+  const CertStatus cert_status = cert_verify_result.cert_status;
+  if (transport_security_state_ &&
+      (result == OK ||
+       (IsCertificateError(result) && IsCertStatusMinorError(cert_status))) &&
+      !transport_security_state_->CheckPublicKeyPins(
+          hostname_,
+          cert_verify_result.is_issued_by_known_root,
+          cert_verify_result.public_key_hashes,
+          &verify_details_->pinning_failure_log)) {
+    result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
+  }
+
+  if (result != OK) {
+    std::string error_string = ErrorToString(result);
+    error_details_ = StringPrintf("Failed to verify certificate chain: %s",
+                                  error_string.c_str());
+    DLOG(WARNING) << error_details_;
+  }
+
+  // Exit DoLoop and return the result to the caller to VerifyProof.
+  DCHECK_EQ(STATE_NONE, next_state_);
+  return result;
+}
+
+bool ProofVerifierChromium::Job::VerifySignature(const string& signed_data,
+                                                 const string& signature,
+                                                 const string& cert) {
+  StringPiece spki;
+  if (!asn1::ExtractSPKIFromDERCert(cert, &spki)) {
+    DLOG(WARNING) << "ExtractSPKIFromDERCert failed";
+    return false;
+  }
+
+  crypto::SignatureVerifier verifier;
+
+  size_t size_bits;
+  X509Certificate::PublicKeyType type;
+  X509Certificate::GetPublicKeyInfo(cert_->os_cert_handle(), &size_bits,
+                                    &type);
+  if (type == X509Certificate::kPublicKeyTypeRSA) {
+    crypto::SignatureVerifier::HashAlgorithm hash_alg =
+        crypto::SignatureVerifier::SHA256;
+    crypto::SignatureVerifier::HashAlgorithm mask_hash_alg = hash_alg;
+    unsigned int hash_len = 32;  // 32 is the length of a SHA-256 hash.
+
+    bool ok = verifier.VerifyInitRSAPSS(
+        hash_alg, mask_hash_alg, hash_len,
+        reinterpret_cast<const uint8*>(signature.data()), signature.size(),
+        reinterpret_cast<const uint8*>(spki.data()), spki.size());
+    if (!ok) {
+      DLOG(WARNING) << "VerifyInitRSAPSS failed";
+      return false;
+    }
+  } else if (type == X509Certificate::kPublicKeyTypeECDSA) {
+    // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
+    // RFC 5758:
+    //   ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+    //        us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
+    //   ...
+    //   When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
+    //   ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
+    //   as an AlgorithmIdentifier, the encoding MUST omit the parameters
+    //   field.  That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
+    //   component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
+    //   SHA384, or ecdsa-with-SHA512.
+    // See also RFC 5480, Appendix A.
+    static const uint8 kECDSAWithSHA256AlgorithmID[] = {
+      0x30, 0x0a,
+        0x06, 0x08,
+          0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
+    };
+
+    if (!verifier.VerifyInit(
+            kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
+            reinterpret_cast<const uint8*>(signature.data()),
+            signature.size(),
+            reinterpret_cast<const uint8*>(spki.data()),
+            spki.size())) {
+      DLOG(WARNING) << "VerifyInit failed";
+      return false;
+    }
+  } else {
+    LOG(ERROR) << "Unsupported public key type " << type;
+    return false;
+  }
+
+  verifier.VerifyUpdate(reinterpret_cast<const uint8*>(kProofSignatureLabel),
+                        sizeof(kProofSignatureLabel));
+  verifier.VerifyUpdate(reinterpret_cast<const uint8*>(signed_data.data()),
+                        signed_data.size());
+
+  if (!verifier.VerifyFinal()) {
+    DLOG(WARNING) << "VerifyFinal failed";
+    return false;
+  }
+
+  DVLOG(1) << "VerifyFinal success";
+  return true;
+}
+
+ProofVerifierChromium::ProofVerifierChromium(
+    CertVerifier* cert_verifier,
+    TransportSecurityState* transport_security_state)
+    : cert_verifier_(cert_verifier),
+      transport_security_state_(transport_security_state) {
+}
+
+ProofVerifierChromium::~ProofVerifierChromium() {
+  STLDeleteElements(&active_jobs_);
+}
+
+QuicAsyncStatus ProofVerifierChromium::VerifyProof(
+    const std::string& hostname,
+    const std::string& server_config,
+    const std::vector<std::string>& certs,
+    const std::string& signature,
+    const ProofVerifyContext* verify_context,
+    std::string* error_details,
+    scoped_ptr<ProofVerifyDetails>* verify_details,
+    ProofVerifierCallback* callback) {
+  if (!verify_context) {
+    *error_details = "Missing context";
+    return QUIC_FAILURE;
+  }
+  const ProofVerifyContextChromium* chromium_context =
+      reinterpret_cast<const ProofVerifyContextChromium*>(verify_context);
+  scoped_ptr<Job> job(new Job(this,
+                              cert_verifier_,
+                              transport_security_state_,
+                              chromium_context->net_log));
+  QuicAsyncStatus status = job->VerifyProof(hostname, server_config, certs,
+                                            signature, error_details,
+                                            verify_details, callback);
+  if (status == QUIC_PENDING) {
+    active_jobs_.insert(job.release());
+  }
+  return status;
+}
+
+void ProofVerifierChromium::OnJobComplete(Job* job) {
+  active_jobs_.erase(job);
+  delete job;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/proof_verifier_chromium.h b/net/quic/crypto/proof_verifier_chromium.h
new file mode 100644
index 0000000..7b305e2
--- /dev/null
+++ b/net/quic/crypto/proof_verifier_chromium.h
@@ -0,0 +1,91 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
+#define NET_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_verifier.h"
+
+namespace net {
+
+class CertVerifier;
+class TransportSecurityState;
+
+// ProofVerifyDetailsChromium is the implementation-specific information that a
+// ProofVerifierChromium returns about a certificate verification.
+class NET_EXPORT_PRIVATE ProofVerifyDetailsChromium
+    : public ProofVerifyDetails {
+ public:
+
+  // ProofVerifyDetails implementation
+  virtual ProofVerifyDetails* Clone() const OVERRIDE;
+
+  CertVerifyResult cert_verify_result;
+
+  // pinning_failure_log contains a message produced by
+  // TransportSecurityState::DomainState::CheckPublicKeyPins in the event of a
+  // pinning failure. It is a (somewhat) human-readable string.
+  std::string pinning_failure_log;
+};
+
+// ProofVerifyContextChromium is the implementation-specific information that a
+// ProofVerifierChromium needs in order to log correctly.
+struct ProofVerifyContextChromium : public ProofVerifyContext {
+ public:
+  explicit ProofVerifyContextChromium(const BoundNetLog& net_log)
+      : net_log(net_log) {}
+
+  BoundNetLog net_log;
+};
+
+// ProofVerifierChromium implements the QUIC ProofVerifier interface.  It is
+// capable of handling multiple simultaneous requests.
+class NET_EXPORT_PRIVATE ProofVerifierChromium : public ProofVerifier {
+ public:
+  ProofVerifierChromium(CertVerifier* cert_verifier,
+                        TransportSecurityState* transport_security_state);
+  virtual ~ProofVerifierChromium();
+
+  // ProofVerifier interface
+  virtual QuicAsyncStatus VerifyProof(
+      const std::string& hostname,
+      const std::string& server_config,
+      const std::vector<std::string>& certs,
+      const std::string& signature,
+      const ProofVerifyContext* verify_context,
+      std::string* error_details,
+      scoped_ptr<ProofVerifyDetails>* verify_details,
+      ProofVerifierCallback* callback) OVERRIDE;
+
+ private:
+  class Job;
+  typedef std::set<Job*> JobSet;
+
+  void OnJobComplete(Job* job);
+
+  // Set owning pointers to active jobs.
+  JobSet active_jobs_;
+
+  // Underlying verifier used to verify certificates.
+  CertVerifier* const cert_verifier_;
+
+  TransportSecurityState* const transport_security_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProofVerifierChromium);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
diff --git a/net/quic/crypto/quic_crypto_client_config.cc b/net/quic/crypto/quic_crypto_client_config.cc
new file mode 100644
index 0000000..90dc265
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_client_config.cc
@@ -0,0 +1,866 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_crypto_client_config.h"
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "net/quic/crypto/cert_compressor.h"
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/curve25519_key_exchange.h"
+#include "net/quic/crypto/key_exchange.h"
+#include "net/quic/crypto/p256_key_exchange.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::find;
+using std::make_pair;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+enum ServerConfigState {
+  // WARNING: Do not change the numerical values of any of server config state.
+  // Do not remove deprecated server config states - just comment them as
+  // deprecated.
+  SERVER_CONFIG_EMPTY = 0,
+  SERVER_CONFIG_INVALID = 1,
+  SERVER_CONFIG_CORRUPTED = 2,
+  SERVER_CONFIG_EXPIRED = 3,
+  SERVER_CONFIG_INVALID_EXPIRY = 4,
+
+  // NOTE: Add new server config states only immediately above this line. Make
+  // sure to update the QuicServerConfigState enum in
+  // tools/metrics/histograms/histograms.xml accordingly.
+  SERVER_CONFIG_COUNT
+};
+
+void RecordServerConfigState(ServerConfigState server_config_state) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicClientHelloServerConfigState",
+                            server_config_state, SERVER_CONFIG_COUNT);
+}
+
+}  // namespace
+
+QuicCryptoClientConfig::QuicCryptoClientConfig()
+    : disable_ecdsa_(false) {}
+
+QuicCryptoClientConfig::~QuicCryptoClientConfig() {
+  STLDeleteValues(&cached_states_);
+}
+
+QuicCryptoClientConfig::CachedState::CachedState()
+    : server_config_valid_(false),
+      generation_counter_(0) {}
+
+QuicCryptoClientConfig::CachedState::~CachedState() {}
+
+bool QuicCryptoClientConfig::CachedState::IsComplete(QuicWallTime now) const {
+  if (server_config_.empty()) {
+    RecordServerConfigState(SERVER_CONFIG_EMPTY);
+    return false;
+  }
+
+  if (!server_config_valid_) {
+    RecordServerConfigState(SERVER_CONFIG_INVALID);
+    return false;
+  }
+
+  const CryptoHandshakeMessage* scfg = GetServerConfig();
+  if (!scfg) {
+    // Should be impossible short of cache corruption.
+    DCHECK(false);
+    RecordServerConfigState(SERVER_CONFIG_CORRUPTED);
+    return false;
+  }
+
+  uint64 expiry_seconds;
+  if (scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) {
+    RecordServerConfigState(SERVER_CONFIG_INVALID_EXPIRY);
+    return false;
+  }
+  if (now.ToUNIXSeconds() >= expiry_seconds) {
+    UMA_HISTOGRAM_CUSTOM_TIMES(
+        "Net.QuicClientHelloServerConfig.InvalidDuration",
+        base::TimeDelta::FromSeconds(now.ToUNIXSeconds() - expiry_seconds),
+        base::TimeDelta::FromMinutes(1), base::TimeDelta::FromDays(20), 50);
+    RecordServerConfigState(SERVER_CONFIG_EXPIRED);
+    return false;
+  }
+
+  return true;
+}
+
+bool QuicCryptoClientConfig::CachedState::IsEmpty() const {
+  return server_config_.empty();
+}
+
+const CryptoHandshakeMessage*
+QuicCryptoClientConfig::CachedState::GetServerConfig() const {
+  if (server_config_.empty()) {
+    return nullptr;
+  }
+
+  if (!scfg_.get()) {
+    scfg_.reset(CryptoFramer::ParseMessage(server_config_));
+    DCHECK(scfg_.get());
+  }
+  return scfg_.get();
+}
+
+QuicErrorCode QuicCryptoClientConfig::CachedState::SetServerConfig(
+    StringPiece server_config, QuicWallTime now, string* error_details) {
+  const bool matches_existing = server_config == server_config_;
+
+  // Even if the new server config matches the existing one, we still wish to
+  // reject it if it has expired.
+  scoped_ptr<CryptoHandshakeMessage> new_scfg_storage;
+  const CryptoHandshakeMessage* new_scfg;
+
+  if (!matches_existing) {
+    new_scfg_storage.reset(CryptoFramer::ParseMessage(server_config));
+    new_scfg = new_scfg_storage.get();
+  } else {
+    new_scfg = GetServerConfig();
+  }
+
+  if (!new_scfg) {
+    *error_details = "SCFG invalid";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  uint64 expiry_seconds;
+  if (new_scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) {
+    *error_details = "SCFG missing EXPY";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  if (now.ToUNIXSeconds() >= expiry_seconds) {
+    *error_details = "SCFG has expired";
+    return QUIC_CRYPTO_SERVER_CONFIG_EXPIRED;
+  }
+
+  if (!matches_existing) {
+    server_config_ = server_config.as_string();
+    SetProofInvalid();
+    scfg_.reset(new_scfg_storage.release());
+  }
+  return QUIC_NO_ERROR;
+}
+
+void QuicCryptoClientConfig::CachedState::InvalidateServerConfig() {
+  server_config_.clear();
+  scfg_.reset();
+  SetProofInvalid();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProof(const vector<string>& certs,
+                                                   StringPiece signature) {
+  bool has_changed =
+      signature != server_config_sig_ || certs_.size() != certs.size();
+
+  if (!has_changed) {
+    for (size_t i = 0; i < certs_.size(); i++) {
+      if (certs_[i] != certs[i]) {
+        has_changed = true;
+        break;
+      }
+    }
+  }
+
+  if (!has_changed) {
+    return;
+  }
+
+  // If the proof has changed then it needs to be revalidated.
+  SetProofInvalid();
+  certs_ = certs;
+  server_config_sig_ = signature.as_string();
+}
+
+void QuicCryptoClientConfig::CachedState::Clear() {
+  server_config_.clear();
+  source_address_token_.clear();
+  certs_.clear();
+  server_config_sig_.clear();
+  server_config_valid_ = false;
+  proof_verify_details_.reset();
+  scfg_.reset();
+  ++generation_counter_;
+}
+
+void QuicCryptoClientConfig::CachedState::ClearProof() {
+  SetProofInvalid();
+  certs_.clear();
+  server_config_sig_.clear();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofValid() {
+  server_config_valid_ = true;
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofInvalid() {
+  server_config_valid_ = false;
+  ++generation_counter_;
+}
+
+bool QuicCryptoClientConfig::CachedState::Initialize(
+    StringPiece server_config,
+    StringPiece source_address_token,
+    const vector<string>& certs,
+    StringPiece signature,
+    QuicWallTime now) {
+  DCHECK(server_config_.empty());
+
+  if (server_config.empty()) {
+    return false;
+  }
+
+  string error_details;
+  QuicErrorCode error = SetServerConfig(server_config, now,
+                                        &error_details);
+  if (error != QUIC_NO_ERROR) {
+    DVLOG(1) << "SetServerConfig failed with " << error_details;
+    return false;
+  }
+
+  signature.CopyToString(&server_config_sig_);
+  source_address_token.CopyToString(&source_address_token_);
+  certs_ = certs;
+  return true;
+}
+
+const string& QuicCryptoClientConfig::CachedState::server_config() const {
+  return server_config_;
+}
+
+const string&
+QuicCryptoClientConfig::CachedState::source_address_token() const {
+  return source_address_token_;
+}
+
+const vector<string>& QuicCryptoClientConfig::CachedState::certs() const {
+  return certs_;
+}
+
+const string& QuicCryptoClientConfig::CachedState::signature() const {
+  return server_config_sig_;
+}
+
+bool QuicCryptoClientConfig::CachedState::proof_valid() const {
+  return server_config_valid_;
+}
+
+uint64 QuicCryptoClientConfig::CachedState::generation_counter() const {
+  return generation_counter_;
+}
+
+const ProofVerifyDetails*
+QuicCryptoClientConfig::CachedState::proof_verify_details() const {
+  return proof_verify_details_.get();
+}
+
+void QuicCryptoClientConfig::CachedState::set_source_address_token(
+    StringPiece token) {
+  source_address_token_ = token.as_string();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofVerifyDetails(
+    ProofVerifyDetails* details) {
+  proof_verify_details_.reset(details);
+}
+
+void QuicCryptoClientConfig::CachedState::InitializeFrom(
+    const QuicCryptoClientConfig::CachedState& other) {
+  DCHECK(server_config_.empty());
+  DCHECK(!server_config_valid_);
+  server_config_ = other.server_config_;
+  source_address_token_ = other.source_address_token_;
+  certs_ = other.certs_;
+  server_config_sig_ = other.server_config_sig_;
+  server_config_valid_ = other.server_config_valid_;
+  if (other.proof_verify_details_.get() != nullptr) {
+    proof_verify_details_.reset(other.proof_verify_details_->Clone());
+  }
+  ++generation_counter_;
+}
+
+void QuicCryptoClientConfig::SetDefaults() {
+  // Key exchange methods.
+  kexs.resize(2);
+  kexs[0] = kC255;
+  kexs[1] = kP256;
+
+  // Authenticated encryption algorithms. Prefer ChaCha20 by default.
+  aead.clear();
+  if (ChaCha20Poly1305Encrypter::IsSupported()) {
+    aead.push_back(kCC12);
+  }
+  aead.push_back(kAESG);
+
+  disable_ecdsa_ = false;
+}
+
+QuicCryptoClientConfig::CachedState* QuicCryptoClientConfig::LookupOrCreate(
+    const QuicServerId& server_id) {
+  CachedStateMap::const_iterator it = cached_states_.find(server_id);
+  if (it != cached_states_.end()) {
+    return it->second;
+  }
+
+  CachedState* cached = new CachedState;
+  cached_states_.insert(make_pair(server_id, cached));
+  PopulateFromCanonicalConfig(server_id, cached);
+  return cached;
+}
+
+void QuicCryptoClientConfig::ClearCachedStates() {
+  for (CachedStateMap::const_iterator it = cached_states_.begin();
+       it != cached_states_.end(); ++it) {
+    it->second->Clear();
+  }
+}
+
+void QuicCryptoClientConfig::FillInchoateClientHello(
+    const QuicServerId& server_id,
+    const QuicVersion preferred_version,
+    const CachedState* cached,
+    QuicCryptoNegotiatedParameters* out_params,
+    CryptoHandshakeMessage* out) const {
+  out->set_tag(kCHLO);
+  out->set_minimum_size(kClientHelloMinimumSize);
+
+  // Server name indication. We only send SNI if it's a valid domain name, as
+  // per the spec.
+  if (CryptoUtils::IsValidSNI(server_id.host())) {
+    out->SetStringPiece(kSNI, server_id.host());
+  }
+  out->SetValue(kVER, QuicVersionToQuicTag(preferred_version));
+
+  if (!user_agent_id_.empty()) {
+    out->SetStringPiece(kUAID, user_agent_id_);
+  }
+
+  if (!cached->source_address_token().empty()) {
+    out->SetStringPiece(kSourceAddressTokenTag, cached->source_address_token());
+  }
+
+  if (server_id.is_https()) {
+    if (disable_ecdsa_) {
+      out->SetTaglist(kPDMD, kX59R, 0);
+    } else {
+      out->SetTaglist(kPDMD, kX509, 0);
+    }
+  }
+
+  if (common_cert_sets) {
+    out->SetStringPiece(kCCS, common_cert_sets->GetCommonHashes());
+  }
+
+  const vector<string>& certs = cached->certs();
+  // We save |certs| in the QuicCryptoNegotiatedParameters so that, if the
+  // client config is being used for multiple connections, another connection
+  // doesn't update the cached certificates and cause us to be unable to
+  // process the server's compressed certificate chain.
+  out_params->cached_certs = certs;
+  if (!certs.empty()) {
+    vector<uint64> hashes;
+    hashes.reserve(certs.size());
+    for (vector<string>::const_iterator i = certs.begin();
+         i != certs.end(); ++i) {
+      hashes.push_back(QuicUtils::FNV1a_64_Hash(i->data(), i->size()));
+    }
+    out->SetVector(kCCRT, hashes);
+  }
+}
+
+QuicErrorCode QuicCryptoClientConfig::FillClientHello(
+    const QuicServerId& server_id,
+    QuicConnectionId connection_id,
+    const QuicVersion preferred_version,
+    const CachedState* cached,
+    QuicWallTime now,
+    QuicRandom* rand,
+    const ChannelIDKey* channel_id_key,
+    QuicCryptoNegotiatedParameters* out_params,
+    CryptoHandshakeMessage* out,
+    string* error_details) const {
+  DCHECK(error_details != nullptr);
+
+  FillInchoateClientHello(server_id, preferred_version, cached,
+                          out_params, out);
+
+  const CryptoHandshakeMessage* scfg = cached->GetServerConfig();
+  if (!scfg) {
+    // This should never happen as our caller should have checked
+    // cached->IsComplete() before calling this function.
+    *error_details = "Handshake not ready";
+    return QUIC_CRYPTO_INTERNAL_ERROR;
+  }
+
+  StringPiece scid;
+  if (!scfg->GetStringPiece(kSCID, &scid)) {
+    *error_details = "SCFG missing SCID";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+  out->SetStringPiece(kSCID, scid);
+
+  const QuicTag* their_aeads;
+  const QuicTag* their_key_exchanges;
+  size_t num_their_aeads, num_their_key_exchanges;
+  if (scfg->GetTaglist(kAEAD, &their_aeads,
+                       &num_their_aeads) != QUIC_NO_ERROR ||
+      scfg->GetTaglist(kKEXS, &their_key_exchanges,
+                       &num_their_key_exchanges) != QUIC_NO_ERROR) {
+    *error_details = "Missing AEAD or KEXS";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  // AEAD: the work loads on the client and server are symmetric. Since the
+  // client is more likely to be CPU-constrained, break the tie by favoring
+  // the client's preference.
+  // Key exchange: the client does more work than the server, so favor the
+  // client's preference.
+  size_t key_exchange_index;
+  if (!QuicUtils::FindMutualTag(
+          aead, their_aeads, num_their_aeads, QuicUtils::LOCAL_PRIORITY,
+          &out_params->aead, nullptr) ||
+      !QuicUtils::FindMutualTag(
+          kexs, their_key_exchanges, num_their_key_exchanges,
+          QuicUtils::LOCAL_PRIORITY, &out_params->key_exchange,
+          &key_exchange_index)) {
+    *error_details = "Unsupported AEAD or KEXS";
+    return QUIC_CRYPTO_NO_SUPPORT;
+  }
+  out->SetTaglist(kAEAD, out_params->aead, 0);
+  out->SetTaglist(kKEXS, out_params->key_exchange, 0);
+
+  StringPiece public_value;
+  if (scfg->GetNthValue24(kPUBS, key_exchange_index, &public_value) !=
+          QUIC_NO_ERROR) {
+    *error_details = "Missing public value";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  StringPiece orbit;
+  if (!scfg->GetStringPiece(kORBT, &orbit) || orbit.size() != kOrbitSize) {
+    *error_details = "SCFG missing OBIT";
+    return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+  }
+
+  CryptoUtils::GenerateNonce(now, rand, orbit, &out_params->client_nonce);
+  out->SetStringPiece(kNONC, out_params->client_nonce);
+  if (!out_params->server_nonce.empty()) {
+    out->SetStringPiece(kServerNonceTag, out_params->server_nonce);
+  }
+
+  switch (out_params->key_exchange) {
+    case kC255:
+      out_params->client_key_exchange.reset(Curve25519KeyExchange::New(
+          Curve25519KeyExchange::NewPrivateKey(rand)));
+      break;
+    case kP256:
+      out_params->client_key_exchange.reset(P256KeyExchange::New(
+          P256KeyExchange::NewPrivateKey()));
+      break;
+    default:
+      DCHECK(false);
+      *error_details = "Configured to support an unknown key exchange";
+      return QUIC_CRYPTO_INTERNAL_ERROR;
+  }
+
+  if (!out_params->client_key_exchange->CalculateSharedKey(
+          public_value, &out_params->initial_premaster_secret)) {
+    *error_details = "Key exchange failure";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+  out->SetStringPiece(kPUBS, out_params->client_key_exchange->public_value());
+
+  if (channel_id_key) {
+    // In order to calculate the encryption key for the CETV block we need to
+    // serialise the client hello as it currently is (i.e. without the CETV
+    // block). For this, the client hello is serialized without padding.
+    const size_t orig_min_size = out->minimum_size();
+    out->set_minimum_size(0);
+
+    CryptoHandshakeMessage cetv;
+    cetv.set_tag(kCETV);
+
+    string hkdf_input;
+    const QuicData& client_hello_serialized = out->GetSerialized();
+    hkdf_input.append(QuicCryptoConfig::kCETVLabel,
+                      strlen(QuicCryptoConfig::kCETVLabel) + 1);
+    hkdf_input.append(reinterpret_cast<char*>(&connection_id),
+                      sizeof(connection_id));
+    hkdf_input.append(client_hello_serialized.data(),
+                      client_hello_serialized.length());
+    hkdf_input.append(cached->server_config());
+
+    string key = channel_id_key->SerializeKey();
+    string signature;
+    if (!channel_id_key->Sign(hkdf_input, &signature)) {
+      *error_details = "Channel ID signature failed";
+      return QUIC_INVALID_CHANNEL_ID_SIGNATURE;
+    }
+
+    cetv.SetStringPiece(kCIDK, key);
+    cetv.SetStringPiece(kCIDS, signature);
+
+    CrypterPair crypters;
+    if (!CryptoUtils::DeriveKeys(out_params->initial_premaster_secret,
+                                 out_params->aead, out_params->client_nonce,
+                                 out_params->server_nonce, hkdf_input,
+                                 CryptoUtils::CLIENT, &crypters,
+                                 nullptr /* subkey secret */)) {
+      *error_details = "Symmetric key setup failed";
+      return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+    }
+
+    const QuicData& cetv_plaintext = cetv.GetSerialized();
+    scoped_ptr<QuicData> cetv_ciphertext(crypters.encrypter->EncryptPacket(
+        0 /* sequence number */,
+        StringPiece() /* associated data */,
+        cetv_plaintext.AsStringPiece()));
+    if (!cetv_ciphertext.get()) {
+      *error_details = "Packet encryption failed";
+      return QUIC_ENCRYPTION_FAILURE;
+    }
+
+    out->SetStringPiece(kCETV, cetv_ciphertext->AsStringPiece());
+    out->MarkDirty();
+
+    out->set_minimum_size(orig_min_size);
+  }
+
+  // Derive the symmetric keys and set up the encrypters and decrypters.
+  // Set the following members of out_params:
+  //   out_params->hkdf_input_suffix
+  //   out_params->initial_crypters
+  out_params->hkdf_input_suffix.clear();
+  out_params->hkdf_input_suffix.append(reinterpret_cast<char*>(&connection_id),
+                                       sizeof(connection_id));
+  const QuicData& client_hello_serialized = out->GetSerialized();
+  out_params->hkdf_input_suffix.append(client_hello_serialized.data(),
+                                       client_hello_serialized.length());
+  out_params->hkdf_input_suffix.append(cached->server_config());
+
+  string hkdf_input;
+  const size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
+  hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size());
+  hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len);
+  hkdf_input.append(out_params->hkdf_input_suffix);
+
+  if (!CryptoUtils::DeriveKeys(
+           out_params->initial_premaster_secret, out_params->aead,
+           out_params->client_nonce, out_params->server_nonce, hkdf_input,
+           CryptoUtils::CLIENT, &out_params->initial_crypters,
+           nullptr /* subkey secret */)) {
+    *error_details = "Symmetric key setup failed";
+    return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+  }
+
+  return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::CacheNewServerConfig(
+    const CryptoHandshakeMessage& message,
+    QuicWallTime now,
+    const vector<string>& cached_certs,
+    CachedState* cached,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+
+  StringPiece scfg;
+  if (!message.GetStringPiece(kSCFG, &scfg)) {
+    *error_details = "Missing SCFG";
+    return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+  }
+
+  QuicErrorCode error = cached->SetServerConfig(scfg, now, error_details);
+  if (error != QUIC_NO_ERROR) {
+    return error;
+  }
+
+  StringPiece token;
+  if (message.GetStringPiece(kSourceAddressTokenTag, &token)) {
+    cached->set_source_address_token(token);
+  }
+
+  StringPiece proof, cert_bytes;
+  bool has_proof = message.GetStringPiece(kPROF, &proof);
+  bool has_cert = message.GetStringPiece(kCertificateTag, &cert_bytes);
+  if (has_proof && has_cert) {
+    vector<string> certs;
+    if (!CertCompressor::DecompressChain(cert_bytes, cached_certs,
+                                         common_cert_sets, &certs)) {
+      *error_details = "Certificate data invalid";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    cached->SetProof(certs, proof);
+  } else {
+    if (proof_verifier() != nullptr) {
+      // Secure QUIC: clear existing proof as we have been sent a new SCFG
+      // without matching proof/certs.
+      cached->ClearProof();
+    }
+
+    if (has_proof && !has_cert) {
+      *error_details = "Certificate missing";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    if (!has_proof && has_cert) {
+      *error_details = "Proof missing";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+  }
+
+  return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::ProcessRejection(
+    const CryptoHandshakeMessage& rej,
+    QuicWallTime now,
+    CachedState* cached,
+    bool is_https,
+    QuicCryptoNegotiatedParameters* out_params,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+
+  if (rej.tag() != kREJ) {
+    *error_details = "Message is not REJ";
+    return QUIC_CRYPTO_INTERNAL_ERROR;
+  }
+
+  QuicErrorCode error = CacheNewServerConfig(rej, now, out_params->cached_certs,
+                                             cached, error_details);
+  if (error != QUIC_NO_ERROR) {
+    return error;
+  }
+
+  StringPiece nonce;
+  if (rej.GetStringPiece(kServerNonceTag, &nonce)) {
+    out_params->server_nonce = nonce.as_string();
+  }
+
+  const uint32* reject_reasons;
+  size_t num_reject_reasons;
+  COMPILE_ASSERT(sizeof(QuicTag) == sizeof(uint32), header_out_of_sync);
+  if (rej.GetTaglist(kRREJ, &reject_reasons,
+                     &num_reject_reasons) == QUIC_NO_ERROR) {
+    uint32 packed_error = 0;
+    for (size_t i = 0; i < num_reject_reasons; ++i) {
+      // HANDSHAKE_OK is 0 and don't report that as error.
+      if (reject_reasons[i] == HANDSHAKE_OK || reject_reasons[i] >= 32) {
+        continue;
+      }
+      HandshakeFailureReason reason =
+          static_cast<HandshakeFailureReason>(reject_reasons[i]);
+      packed_error |= 1 << (reason - 1);
+    }
+    DVLOG(1) << "Reasons for rejection: " << packed_error;
+    if (is_https) {
+      UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicClientHelloRejectReasons.Secure",
+                                  packed_error);
+    } else {
+      UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicClientHelloRejectReasons.Insecure",
+                                  packed_error);
+    }
+  }
+
+  return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::ProcessServerHello(
+    const CryptoHandshakeMessage& server_hello,
+    QuicConnectionId connection_id,
+    const QuicVersionVector& negotiated_versions,
+    CachedState* cached,
+    QuicCryptoNegotiatedParameters* out_params,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+
+  if (server_hello.tag() != kSHLO) {
+    *error_details = "Bad tag";
+    return QUIC_INVALID_CRYPTO_MESSAGE_TYPE;
+  }
+
+  const QuicTag* supported_version_tags;
+  size_t num_supported_versions;
+
+  if (server_hello.GetTaglist(kVER, &supported_version_tags,
+                              &num_supported_versions) != QUIC_NO_ERROR) {
+    *error_details = "server hello missing version list";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+  if (!negotiated_versions.empty()) {
+    bool mismatch = num_supported_versions != negotiated_versions.size();
+    for (size_t i = 0; i < num_supported_versions && !mismatch; ++i) {
+      mismatch = QuicTagToQuicVersion(supported_version_tags[i]) !=
+          negotiated_versions[i];
+    }
+    // The server sent a list of supported versions, and the connection
+    // reports that there was a version negotiation during the handshake.
+      // Ensure that these two lists are identical.
+    if (mismatch) {
+      *error_details = "Downgrade attack detected";
+      return QUIC_VERSION_NEGOTIATION_MISMATCH;
+    }
+  }
+
+  // Learn about updated source address tokens.
+  StringPiece token;
+  if (server_hello.GetStringPiece(kSourceAddressTokenTag, &token)) {
+    cached->set_source_address_token(token);
+  }
+
+  // TODO(agl):
+  //   learn about updated SCFGs.
+
+  StringPiece public_value;
+  if (!server_hello.GetStringPiece(kPUBS, &public_value)) {
+    *error_details = "server hello missing forward secure public value";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  if (!out_params->client_key_exchange->CalculateSharedKey(
+          public_value, &out_params->forward_secure_premaster_secret)) {
+    *error_details = "Key exchange failure";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  string hkdf_input;
+  const size_t label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1;
+  hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size());
+  hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel, label_len);
+  hkdf_input.append(out_params->hkdf_input_suffix);
+
+  if (!CryptoUtils::DeriveKeys(
+           out_params->forward_secure_premaster_secret, out_params->aead,
+           out_params->client_nonce, out_params->server_nonce, hkdf_input,
+           CryptoUtils::CLIENT, &out_params->forward_secure_crypters,
+           &out_params->subkey_secret)) {
+    *error_details = "Symmetric key setup failed";
+    return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+  }
+
+  return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::ProcessServerConfigUpdate(
+    const CryptoHandshakeMessage& server_config_update,
+    QuicWallTime now,
+    CachedState* cached,
+    QuicCryptoNegotiatedParameters* out_params,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+
+  if (server_config_update.tag() != kSCUP) {
+    *error_details = "ServerConfigUpdate must have kSCUP tag.";
+    return QUIC_INVALID_CRYPTO_MESSAGE_TYPE;
+  }
+
+  return CacheNewServerConfig(server_config_update, now,
+                              out_params->cached_certs, cached, error_details);
+}
+
+ProofVerifier* QuicCryptoClientConfig::proof_verifier() const {
+  return proof_verifier_.get();
+}
+
+void QuicCryptoClientConfig::SetProofVerifier(ProofVerifier* verifier) {
+  proof_verifier_.reset(verifier);
+}
+
+ChannelIDSource* QuicCryptoClientConfig::channel_id_source() const {
+  return channel_id_source_.get();
+}
+
+void QuicCryptoClientConfig::SetChannelIDSource(ChannelIDSource* source) {
+  channel_id_source_.reset(source);
+}
+
+void QuicCryptoClientConfig::InitializeFrom(
+    const QuicServerId& server_id,
+    const QuicServerId& canonical_server_id,
+    QuicCryptoClientConfig* canonical_crypto_config) {
+  CachedState* canonical_cached =
+      canonical_crypto_config->LookupOrCreate(canonical_server_id);
+  if (!canonical_cached->proof_valid()) {
+    return;
+  }
+  CachedState* cached = LookupOrCreate(server_id);
+  cached->InitializeFrom(*canonical_cached);
+}
+
+void QuicCryptoClientConfig::AddCanonicalSuffix(const string& suffix) {
+  canoncial_suffixes_.push_back(suffix);
+}
+
+void QuicCryptoClientConfig::PreferAesGcm() {
+  DCHECK(!aead.empty());
+  if (aead.size() <= 1) {
+    return;
+  }
+  QuicTagVector::iterator pos = find(aead.begin(), aead.end(), kAESG);
+  if (pos != aead.end()) {
+    aead.erase(pos);
+    aead.insert(aead.begin(), kAESG);
+  }
+}
+
+void QuicCryptoClientConfig::DisableEcdsa() {
+  disable_ecdsa_ = true;
+}
+
+void QuicCryptoClientConfig::PopulateFromCanonicalConfig(
+    const QuicServerId& server_id,
+    CachedState* server_state) {
+  DCHECK(server_state->IsEmpty());
+  size_t i = 0;
+  for (; i < canoncial_suffixes_.size(); ++i) {
+    if (EndsWith(server_id.host(), canoncial_suffixes_[i], false)) {
+      break;
+    }
+  }
+  if (i == canoncial_suffixes_.size())
+    return;
+
+  QuicServerId suffix_server_id(canoncial_suffixes_[i], server_id.port(),
+                                server_id.is_https(),
+                                server_id.privacy_mode());
+  if (!ContainsKey(canonical_server_map_, suffix_server_id)) {
+    // This is the first host we've seen which matches the suffix, so make it
+    // canonical.
+    canonical_server_map_[suffix_server_id] = server_id;
+    return;
+  }
+
+  const QuicServerId& canonical_server_id =
+      canonical_server_map_[suffix_server_id];
+  CachedState* canonical_state = cached_states_[canonical_server_id];
+  if (!canonical_state->proof_valid()) {
+    return;
+  }
+
+  // Update canonical version to point at the "most recent" entry.
+  canonical_server_map_[suffix_server_id] = server_id;
+
+  server_state->InitializeFrom(*canonical_state);
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_crypto_client_config.h b/net/quic/crypto/quic_crypto_client_config.h
new file mode 100644
index 0000000..39c2508
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_client_config.h
@@ -0,0 +1,317 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
+#define NET_QUIC_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_id.h"
+
+namespace net {
+
+class ChannelIDKey;
+class ChannelIDSource;
+class CryptoHandshakeMessage;
+class ProofVerifier;
+class ProofVerifyDetails;
+class QuicRandom;
+
+// QuicCryptoClientConfig contains crypto-related configuration settings for a
+// client. Note that this object isn't thread-safe. It's designed to be used on
+// a single thread at a time.
+class NET_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig {
+ public:
+  // A CachedState contains the information that the client needs in order to
+  // perform a 0-RTT handshake with a server. This information can be reused
+  // over several connections to the same server.
+  class NET_EXPORT_PRIVATE CachedState {
+   public:
+    CachedState();
+    ~CachedState();
+
+    // IsComplete returns true if this object contains enough information to
+    // perform a handshake with the server. |now| is used to judge whether any
+    // cached server config has expired.
+    bool IsComplete(QuicWallTime now) const;
+
+    // IsEmpty returns true if |server_config_| is empty.
+    bool IsEmpty() const;
+
+    // GetServerConfig returns the parsed contents of |server_config|, or
+    // nullptr if |server_config| is empty. The return value is owned by this
+    // object and is destroyed when this object is.
+    const CryptoHandshakeMessage* GetServerConfig() const;
+
+    // SetServerConfig checks that |server_config| parses correctly and stores
+    // it in |server_config_|. |now| is used to judge whether |server_config|
+    // has expired.
+    QuicErrorCode SetServerConfig(base::StringPiece server_config,
+                                  QuicWallTime now,
+                                  std::string* error_details);
+
+    // InvalidateServerConfig clears the cached server config (if any).
+    void InvalidateServerConfig();
+
+    // SetProof stores a certificate chain and signature.
+    void SetProof(const std::vector<std::string>& certs,
+                  base::StringPiece signature);
+
+    // Clears all the data.
+    void Clear();
+
+    // Clears the certificate chain and signature and invalidates the proof.
+    void ClearProof();
+
+    // SetProofValid records that the certificate chain and signature have been
+    // validated and that it's safe to assume that the server is legitimate.
+    // (Note: this does not check the chain or signature.)
+    void SetProofValid();
+
+    // If the server config or the proof has changed then it needs to be
+    // revalidated. Helper function to keep server_config_valid_ and
+    // generation_counter_ in sync.
+    void SetProofInvalid();
+
+    const std::string& server_config() const;
+    const std::string& source_address_token() const;
+    const std::vector<std::string>& certs() const;
+    const std::string& signature() const;
+    bool proof_valid() const;
+    uint64 generation_counter() const;
+    const ProofVerifyDetails* proof_verify_details() const;
+
+    void set_source_address_token(base::StringPiece token);
+
+    // SetProofVerifyDetails takes ownership of |details|.
+    void SetProofVerifyDetails(ProofVerifyDetails* details);
+
+    // Copy the |server_config_|, |source_address_token_|, |certs_| and
+    // |server_config_sig_| from the |other|.  The remaining fields,
+    // |generation_counter_|, |proof_verify_details_|, and |scfg_| remain
+    // unchanged.
+    void InitializeFrom(const CachedState& other);
+
+    // Initializes this cached state based on the arguments provided.
+    // Returns false if there is a problem parsing the server config.
+    bool Initialize(base::StringPiece server_config,
+                    base::StringPiece source_address_token,
+                    const std::vector<std::string>& certs,
+                    base::StringPiece signature,
+                    QuicWallTime now);
+
+   private:
+    std::string server_config_;         // A serialized handshake message.
+    std::string source_address_token_;  // An opaque proof of IP ownership.
+    std::vector<std::string> certs_;    // A list of certificates in leaf-first
+                                        // order.
+    std::string server_config_sig_;     // A signature of |server_config_|.
+    bool server_config_valid_;          // True if |server_config_| is correctly
+                                        // signed and |certs_| has been
+                                        // validated.
+    // Generation counter associated with the |server_config_|, |certs_| and
+    // |server_config_sig_| combination. It is incremented whenever we set
+    // server_config_valid_ to false.
+    uint64 generation_counter_;
+
+    scoped_ptr<ProofVerifyDetails> proof_verify_details_;
+
+    // scfg contains the cached, parsed value of |server_config|.
+    mutable scoped_ptr<CryptoHandshakeMessage> scfg_;
+
+    DISALLOW_COPY_AND_ASSIGN(CachedState);
+  };
+
+  QuicCryptoClientConfig();
+  ~QuicCryptoClientConfig();
+
+  // Sets the members to reasonable, default values.
+  void SetDefaults();
+
+  // LookupOrCreate returns a CachedState for the given |server_id|. If no such
+  // CachedState currently exists, it will be created and cached.
+  CachedState* LookupOrCreate(const QuicServerId& server_id);
+
+  // Delete all CachedState objects from cached_states_.
+  void ClearCachedStates();
+
+  // FillInchoateClientHello sets |out| to be a CHLO message that elicits a
+  // source-address token or SCFG from a server. If |cached| is non-nullptr, the
+  // source-address token will be taken from it. |out_params| is used in order
+  // to store the cached certs that were sent as hints to the server in
+  // |out_params->cached_certs|. |preferred_version| is the version of the
+  // QUIC protocol that this client chose to use initially. This allows the
+  // server to detect downgrade attacks.
+  void FillInchoateClientHello(const QuicServerId& server_id,
+                               const QuicVersion preferred_version,
+                               const CachedState* cached,
+                               QuicCryptoNegotiatedParameters* out_params,
+                               CryptoHandshakeMessage* out) const;
+
+  // FillClientHello sets |out| to be a CHLO message based on the configuration
+  // of this object. This object must have cached enough information about
+  // the server's hostname in order to perform a handshake. This can be checked
+  // with the |IsComplete| member of |CachedState|.
+  //
+  // |now| and |rand| are used to generate the nonce and |out_params| is
+  // filled with the results of the handshake that the server is expected to
+  // accept. |preferred_version| is the version of the QUIC protocol that this
+  // client chose to use initially. This allows the server to detect downgrade
+  // attacks.
+  //
+  // If |channel_id_key| is not null, it is used to sign a secret value derived
+  // from the client and server's keys, and the Channel ID public key and the
+  // signature are placed in the CETV value of the CHLO.
+  QuicErrorCode FillClientHello(const QuicServerId& server_id,
+                                QuicConnectionId connection_id,
+                                const QuicVersion preferred_version,
+                                const CachedState* cached,
+                                QuicWallTime now,
+                                QuicRandom* rand,
+                                const ChannelIDKey* channel_id_key,
+                                QuicCryptoNegotiatedParameters* out_params,
+                                CryptoHandshakeMessage* out,
+                                std::string* error_details) const;
+
+  // ProcessRejection processes a REJ message from a server and updates the
+  // cached information about that server. After this, |IsComplete| may return
+  // true for that server's CachedState. If the rejection message contains
+  // state about a future handshake (i.e. an nonce value from the server), then
+  // it will be saved in |out_params|. |now| is used to judge whether the
+  // server config in the rejection message has expired. |is_https| is used to
+  // track reject reason for secure vs insecure QUIC.
+  QuicErrorCode ProcessRejection(const CryptoHandshakeMessage& rej,
+                                 QuicWallTime now,
+                                 CachedState* cached,
+                                 bool is_https,
+                                 QuicCryptoNegotiatedParameters* out_params,
+                                 std::string* error_details);
+
+  // ProcessServerHello processes the message in |server_hello|, updates the
+  // cached information about that server, writes the negotiated parameters to
+  // |out_params| and returns QUIC_NO_ERROR. If |server_hello| is unacceptable
+  // then it puts an error message in |error_details| and returns an error
+  // code. |negotiated_versions| contains the list of version, if any, that were
+  // present in a version negotiation packet previously recevied from the
+  // server. The contents of this list will be compared against the list of
+  // versions provided in the VER tag of the server hello.
+  QuicErrorCode ProcessServerHello(const CryptoHandshakeMessage& server_hello,
+                                   QuicConnectionId connection_id,
+                                   const QuicVersionVector& negotiated_versions,
+                                   CachedState* cached,
+                                   QuicCryptoNegotiatedParameters* out_params,
+                                   std::string* error_details);
+
+  // Processes the message in |server_update|, updating the cached source
+  // address token, and server config.
+  // If |server_update| is invalid then |error_details| will contain an error
+  // message, and an error code will be returned. If all has gone well
+  // QUIC_NO_ERROR is returned.
+  QuicErrorCode ProcessServerConfigUpdate(
+      const CryptoHandshakeMessage& server_update,
+      QuicWallTime now,
+      CachedState* cached,
+      QuicCryptoNegotiatedParameters* out_params,
+      std::string* error_details);
+
+  ProofVerifier* proof_verifier() const;
+
+  // SetProofVerifier takes ownership of a |ProofVerifier| that clients are
+  // free to use in order to verify certificate chains from servers. If a
+  // ProofVerifier is set then the client will request a certificate chain from
+  // the server.
+  void SetProofVerifier(ProofVerifier* verifier);
+
+  ChannelIDSource* channel_id_source() const;
+
+  // SetChannelIDSource sets a ChannelIDSource that will be called, when the
+  // server supports channel IDs, to obtain a channel ID for signing a message
+  // proving possession of the channel ID. This object takes ownership of
+  // |source|.
+  void SetChannelIDSource(ChannelIDSource* source);
+
+  // Initialize the CachedState from |canonical_crypto_config| for the
+  // |canonical_server_id| as the initial CachedState for |server_id|. We will
+  // copy config data only if |canonical_crypto_config| has valid proof.
+  void InitializeFrom(const QuicServerId& server_id,
+                      const QuicServerId& canonical_server_id,
+                      QuicCryptoClientConfig* canonical_crypto_config);
+
+  // Adds |suffix| as a domain suffix for which the server's crypto config
+  // is expected to be shared among servers with the domain suffix. If a server
+  // matches this suffix, then the server config from another server with the
+  // suffix will be used to initialize the cached state for this server.
+  void AddCanonicalSuffix(const std::string& suffix);
+
+  // Prefers AES-GCM (kAESG) over other AEAD algorithms. Call this method if
+  // the CPU has hardware acceleration for AES-GCM. This method can only be
+  // called after SetDefaults().
+  void PreferAesGcm();
+
+  // Disables the use of ECDSA for proof verification.
+  // Call this method on platforms that do not support ECDSA.
+  // TODO(rch): remove this method when we drop support for Windows XP.
+  void DisableEcdsa();
+
+  // Saves the |user_agent_id| that will be passed in QUIC's CHLO message.
+  void set_user_agent_id(const std::string& user_agent_id) {
+    user_agent_id_ = user_agent_id;
+  }
+
+ private:
+  typedef std::map<QuicServerId, CachedState*> CachedStateMap;
+
+  // CacheNewServerConfig checks for SCFG, STK, PROF, and CRT tags in |message|,
+  // verifies them, and stores them in the cached state if they validate.
+  // This is used on receipt of a REJ from a server, or when a server sends
+  // updated server config during a connection.
+  QuicErrorCode CacheNewServerConfig(
+      const CryptoHandshakeMessage& message,
+      QuicWallTime now,
+      const std::vector<std::string>& cached_certs,
+      CachedState* cached,
+      std::string* error_details);
+
+  // If the suffix of the hostname in |server_id| is in |canoncial_suffixes_|,
+  // then populate |cached| with the canonical cached state from
+  // |canonical_server_map_| for that suffix.
+  void PopulateFromCanonicalConfig(const QuicServerId& server_id,
+                                   CachedState* cached);
+
+  // cached_states_ maps from the server_id to the cached information about
+  // that server.
+  CachedStateMap cached_states_;
+
+  // Contains a map of servers which could share the same server config. Map
+  // from a canonical host suffix/port/scheme to a representative server with
+  // the canonical suffix, which has a plausible set of initial certificates
+  // (or at least server public key).
+  std::map<QuicServerId, QuicServerId> canonical_server_map_;
+
+  // Contains list of suffixes (for exmaple ".c.youtube.com",
+  // ".googlevideo.com") of canoncial hostnames.
+  std::vector<std::string> canoncial_suffixes_;
+
+  scoped_ptr<ProofVerifier> proof_verifier_;
+  scoped_ptr<ChannelIDSource> channel_id_source_;
+
+  // True if ECDSA should be disabled.
+  bool disable_ecdsa_;
+
+  // The |user_agent_id_| passed in QUIC's CHLO message.
+  std::string user_agent_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoClientConfig);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_CRYPTO_CLIENT_CONFIG_H_
diff --git a/net/quic/crypto/quic_crypto_client_config_test.cc b/net/quic/crypto/quic_crypto_client_config_test.cc
new file mode 100644
index 0000000..b02243b
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_client_config_test.cc
@@ -0,0 +1,271 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_crypto_client_config.h"
+
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_server_id.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class TestProofVerifyDetails : public ProofVerifyDetails {
+  virtual ~TestProofVerifyDetails() {}
+
+  // ProofVerifyDetails implementation
+  virtual ProofVerifyDetails* Clone() const OVERRIDE {
+    return new TestProofVerifyDetails;
+  }
+};
+
+}  // namespace
+
+TEST(QuicCryptoClientConfigTest, CachedState_IsEmpty) {
+  QuicCryptoClientConfig::CachedState state;
+  EXPECT_TRUE(state.IsEmpty());
+}
+
+TEST(QuicCryptoClientConfigTest, CachedState_IsComplete) {
+  QuicCryptoClientConfig::CachedState state;
+  EXPECT_FALSE(state.IsComplete(QuicWallTime::FromUNIXSeconds(0)));
+}
+
+TEST(QuicCryptoClientConfigTest, CachedState_GenerationCounter) {
+  QuicCryptoClientConfig::CachedState state;
+  EXPECT_EQ(0u, state.generation_counter());
+  state.SetProofInvalid();
+  EXPECT_EQ(1u, state.generation_counter());
+}
+
+TEST(QuicCryptoClientConfigTest, CachedState_SetProofVerifyDetails) {
+  QuicCryptoClientConfig::CachedState state;
+  EXPECT_TRUE(state.proof_verify_details() == nullptr);
+  ProofVerifyDetails* details = new TestProofVerifyDetails;
+  state.SetProofVerifyDetails(details);
+  EXPECT_EQ(details, state.proof_verify_details());
+}
+
+TEST(QuicCryptoClientConfigTest, CachedState_InitializeFrom) {
+  QuicCryptoClientConfig::CachedState state;
+  QuicCryptoClientConfig::CachedState other;
+  state.set_source_address_token("TOKEN");
+  // TODO(rch): Populate other fields of |state|.
+  other.InitializeFrom(state);
+  EXPECT_EQ(state.server_config(), other.server_config());
+  EXPECT_EQ(state.source_address_token(), other.source_address_token());
+  EXPECT_EQ(state.certs(), other.certs());
+  EXPECT_EQ(1u, other.generation_counter());
+}
+
+TEST(QuicCryptoClientConfigTest, InchoateChlo) {
+  QuicCryptoClientConfig::CachedState state;
+  QuicCryptoClientConfig config;
+  QuicCryptoNegotiatedParameters params;
+  CryptoHandshakeMessage msg;
+  QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED);
+  config.FillInchoateClientHello(server_id, QuicVersionMax(), &state,
+                                 &params, &msg);
+
+  QuicTag cver;
+  EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kVER, &cver));
+  EXPECT_EQ(QuicVersionToQuicTag(QuicVersionMax()), cver);
+}
+
+TEST(QuicCryptoClientConfigTest, PreferAesGcm) {
+  QuicCryptoClientConfig config;
+  config.SetDefaults();
+  if (config.aead.size() > 1)
+    EXPECT_NE(kAESG, config.aead[0]);
+  config.PreferAesGcm();
+  EXPECT_EQ(kAESG, config.aead[0]);
+}
+
+TEST(QuicCryptoClientConfigTest, InchoateChloSecure) {
+  QuicCryptoClientConfig::CachedState state;
+  QuicCryptoClientConfig config;
+  QuicCryptoNegotiatedParameters params;
+  CryptoHandshakeMessage msg;
+  QuicServerId server_id("www.google.com", 443, true, PRIVACY_MODE_DISABLED);
+  config.FillInchoateClientHello(server_id, QuicVersionMax(), &state,
+                                 &params, &msg);
+
+  QuicTag pdmd;
+  EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kPDMD, &pdmd));
+  EXPECT_EQ(kX509, pdmd);
+}
+
+TEST(QuicCryptoClientConfigTest, InchoateChloSecureNoEcdsa) {
+  QuicCryptoClientConfig::CachedState state;
+  QuicCryptoClientConfig config;
+  config.DisableEcdsa();
+  QuicCryptoNegotiatedParameters params;
+  CryptoHandshakeMessage msg;
+  QuicServerId server_id("www.google.com", 443, true, PRIVACY_MODE_DISABLED);
+  config.FillInchoateClientHello(server_id, QuicVersionMax(), &state,
+                                 &params, &msg);
+
+  QuicTag pdmd;
+  EXPECT_EQ(QUIC_NO_ERROR, msg.GetUint32(kPDMD, &pdmd));
+  EXPECT_EQ(kX59R, pdmd);
+}
+
+TEST(QuicCryptoClientConfigTest, FillClientHello) {
+  QuicCryptoClientConfig::CachedState state;
+  QuicCryptoClientConfig config;
+  QuicCryptoNegotiatedParameters params;
+  QuicConnectionId kConnectionId = 1234;
+  string error_details;
+  MockRandom rand;
+  CryptoHandshakeMessage chlo;
+  QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED);
+  config.FillClientHello(server_id,
+                         kConnectionId,
+                         QuicVersionMax(),
+                         &state,
+                         QuicWallTime::Zero(),
+                         &rand,
+                         nullptr,  // channel_id_key
+                         &params,
+                         &chlo,
+                         &error_details);
+
+  // Verify that certain QuicTags have been set correctly in the CHLO.
+  QuicTag cver;
+  EXPECT_EQ(QUIC_NO_ERROR, chlo.GetUint32(kVER, &cver));
+  EXPECT_EQ(QuicVersionToQuicTag(QuicVersionMax()), cver);
+}
+
+TEST(QuicCryptoClientConfigTest, ProcessServerDowngradeAttack) {
+  QuicVersionVector supported_versions = QuicSupportedVersions();
+  if (supported_versions.size() == 1) {
+    // No downgrade attack is possible if the client only supports one version.
+    return;
+  }
+  QuicTagVector supported_version_tags;
+  for (size_t i = supported_versions.size(); i > 0; --i) {
+    supported_version_tags.push_back(
+        QuicVersionToQuicTag(supported_versions[i - 1]));
+  }
+  CryptoHandshakeMessage msg;
+  msg.set_tag(kSHLO);
+  msg.SetVector(kVER, supported_version_tags);
+
+  QuicCryptoClientConfig::CachedState cached;
+  QuicCryptoNegotiatedParameters out_params;
+  string error;
+  QuicCryptoClientConfig config;
+  EXPECT_EQ(QUIC_VERSION_NEGOTIATION_MISMATCH,
+            config.ProcessServerHello(msg, 0, supported_versions,
+                                      &cached, &out_params, &error));
+  EXPECT_EQ("Downgrade attack detected", error);
+}
+
+TEST(QuicCryptoClientConfigTest, InitializeFrom) {
+  QuicCryptoClientConfig config;
+  QuicServerId canonical_server_id("www.google.com", 80, false,
+                                   PRIVACY_MODE_DISABLED);
+  QuicCryptoClientConfig::CachedState* state =
+      config.LookupOrCreate(canonical_server_id);
+  // TODO(rch): Populate other fields of |state|.
+  state->set_source_address_token("TOKEN");
+  state->SetProofValid();
+
+  QuicServerId other_server_id("mail.google.com", 80, false,
+                               PRIVACY_MODE_DISABLED);
+  config.InitializeFrom(other_server_id, canonical_server_id, &config);
+  QuicCryptoClientConfig::CachedState* other =
+      config.LookupOrCreate(other_server_id);
+
+  EXPECT_EQ(state->server_config(), other->server_config());
+  EXPECT_EQ(state->source_address_token(), other->source_address_token());
+  EXPECT_EQ(state->certs(), other->certs());
+  EXPECT_EQ(1u, other->generation_counter());
+}
+
+TEST(QuicCryptoClientConfigTest, Canonical) {
+  QuicCryptoClientConfig config;
+  config.AddCanonicalSuffix(".google.com");
+  QuicServerId canonical_id1("www.google.com", 80, false,
+                             PRIVACY_MODE_DISABLED);
+  QuicServerId canonical_id2("mail.google.com", 80, false,
+                             PRIVACY_MODE_DISABLED);
+  QuicCryptoClientConfig::CachedState* state =
+      config.LookupOrCreate(canonical_id1);
+  // TODO(rch): Populate other fields of |state|.
+  state->set_source_address_token("TOKEN");
+  state->SetProofValid();
+
+  QuicCryptoClientConfig::CachedState* other =
+      config.LookupOrCreate(canonical_id2);
+
+  EXPECT_TRUE(state->IsEmpty());
+  EXPECT_EQ(state->server_config(), other->server_config());
+  EXPECT_EQ(state->source_address_token(), other->source_address_token());
+  EXPECT_EQ(state->certs(), other->certs());
+  EXPECT_EQ(1u, other->generation_counter());
+
+  QuicServerId different_id("mail.google.org", 80, false,
+                            PRIVACY_MODE_DISABLED);
+  EXPECT_TRUE(config.LookupOrCreate(different_id)->IsEmpty());
+}
+
+TEST(QuicCryptoClientConfigTest, CanonicalNotUsedIfNotValid) {
+  QuicCryptoClientConfig config;
+  config.AddCanonicalSuffix(".google.com");
+  QuicServerId canonical_id1("www.google.com", 80, false,
+                             PRIVACY_MODE_DISABLED);
+  QuicServerId canonical_id2("mail.google.com", 80, false,
+                             PRIVACY_MODE_DISABLED);
+  QuicCryptoClientConfig::CachedState* state =
+      config.LookupOrCreate(canonical_id1);
+  // TODO(rch): Populate other fields of |state|.
+  state->set_source_address_token("TOKEN");
+
+  // Do not set the proof as valid, and check that it is not used
+  // as a canonical entry.
+  EXPECT_TRUE(config.LookupOrCreate(canonical_id2)->IsEmpty());
+}
+
+TEST(QuicCryptoClientConfigTest, ClearCachedStates) {
+  QuicCryptoClientConfig config;
+  QuicServerId server_id("www.google.com", 80, false, PRIVACY_MODE_DISABLED);
+  QuicCryptoClientConfig::CachedState* state = config.LookupOrCreate(server_id);
+  // TODO(rch): Populate other fields of |state|.
+  vector<string> certs(1);
+  certs[0] = "Hello Cert";
+  state->SetProof(certs, "signature");
+  state->set_source_address_token("TOKEN");
+  state->SetProofValid();
+  EXPECT_EQ(1u, state->generation_counter());
+
+  // Verify LookupOrCreate returns the same data.
+  QuicCryptoClientConfig::CachedState* other = config.LookupOrCreate(server_id);
+
+  EXPECT_EQ(state, other);
+  EXPECT_EQ(1u, other->generation_counter());
+
+  // Clear the cached states.
+  config.ClearCachedStates();
+
+  // Verify LookupOrCreate doesn't have any data.
+  QuicCryptoClientConfig::CachedState* cleared_cache =
+      config.LookupOrCreate(server_id);
+
+  EXPECT_EQ(state, cleared_cache);
+  EXPECT_FALSE(cleared_cache->proof_valid());
+  EXPECT_TRUE(cleared_cache->server_config().empty());
+  EXPECT_TRUE(cleared_cache->certs().empty());
+  EXPECT_TRUE(cleared_cache->signature().empty());
+  EXPECT_EQ(2u, cleared_cache->generation_counter());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/quic_crypto_server_config.cc b/net/quic/crypto/quic_crypto_server_config.cc
new file mode 100644
index 0000000..6281043
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_server_config.cc
@@ -0,0 +1,1571 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_crypto_server_config.h"
+
+#include <stdlib.h>
+#include <algorithm>
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "crypto/hkdf.h"
+#include "crypto/secure_hash.h"
+#include "net/base/net_util.h"
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/cert_compressor.h"
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_server_config_protobuf.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/curve25519_key_exchange.h"
+#include "net/quic/crypto/ephemeral_key_source.h"
+#include "net/quic/crypto/key_exchange.h"
+#include "net/quic/crypto/local_strike_register_client.h"
+#include "net/quic/crypto/p256_key_exchange.h"
+#include "net/quic/crypto/proof_source.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/crypto/source_address_token.h"
+#include "net/quic/crypto/strike_register.h"
+#include "net/quic/crypto/strike_register_client.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_socket_address_coder.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using crypto::SecureHash;
+using std::map;
+using std::sort;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+string DeriveSourceAddressTokenKey(StringPiece source_address_token_secret) {
+  crypto::HKDF hkdf(source_address_token_secret,
+                    StringPiece() /* no salt */,
+                    "QUIC source address token key",
+                    CryptoSecretBoxer::GetKeySize(),
+                    0 /* no fixed IV needed */,
+                    0 /* no subkey secret */);
+  return hkdf.server_write_key().as_string();
+}
+
+}  // namespace
+
+// ClientHelloInfo contains information about a client hello message that is
+// only kept for as long as it's being processed.
+struct ClientHelloInfo {
+  ClientHelloInfo(const IPEndPoint& in_client_ip, QuicWallTime in_now)
+      : client_ip(in_client_ip),
+        now(in_now),
+        valid_source_address_token(false),
+        client_nonce_well_formed(false),
+        unique(false) {}
+
+  // Inputs to EvaluateClientHello.
+  const IPEndPoint client_ip;
+  const QuicWallTime now;
+
+  // Outputs from EvaluateClientHello.
+  bool valid_source_address_token;
+  bool client_nonce_well_formed;
+  bool unique;
+  StringPiece sni;
+  StringPiece client_nonce;
+  StringPiece server_nonce;
+  StringPiece user_agent_id;
+
+  // Errors from EvaluateClientHello.
+  vector<uint32> reject_reasons;
+  COMPILE_ASSERT(sizeof(QuicTag) == sizeof(uint32), header_out_of_sync);
+};
+
+struct ValidateClientHelloResultCallback::Result {
+  Result(const CryptoHandshakeMessage& in_client_hello,
+         IPEndPoint in_client_ip,
+         QuicWallTime in_now)
+      : client_hello(in_client_hello),
+        info(in_client_ip, in_now),
+        error_code(QUIC_NO_ERROR) {
+  }
+
+  CryptoHandshakeMessage client_hello;
+  ClientHelloInfo info;
+  QuicErrorCode error_code;
+  string error_details;
+};
+
+class ValidateClientHelloHelper {
+ public:
+  ValidateClientHelloHelper(ValidateClientHelloResultCallback::Result* result,
+                            ValidateClientHelloResultCallback* done_cb)
+      : result_(result), done_cb_(done_cb) {
+  }
+
+  ~ValidateClientHelloHelper() {
+    LOG_IF(DFATAL, done_cb_ != nullptr)
+        << "Deleting ValidateClientHelloHelper with a pending callback.";
+  }
+
+  void ValidationComplete(QuicErrorCode error_code, const char* error_details) {
+    result_->error_code = error_code;
+    result_->error_details = error_details;
+    done_cb_->Run(result_);
+    DetachCallback();
+  }
+
+  void StartedAsyncCallback() {
+    DetachCallback();
+  }
+
+ private:
+  void DetachCallback() {
+    LOG_IF(DFATAL, done_cb_ == nullptr) << "Callback already detached.";
+    done_cb_ = nullptr;
+  }
+
+  ValidateClientHelloResultCallback::Result* result_;
+  ValidateClientHelloResultCallback* done_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(ValidateClientHelloHelper);
+};
+
+class VerifyNonceIsValidAndUniqueCallback
+    : public StrikeRegisterClient::ResultCallback {
+ public:
+  VerifyNonceIsValidAndUniqueCallback(
+      ValidateClientHelloResultCallback::Result* result,
+      ValidateClientHelloResultCallback* done_cb)
+      : result_(result), done_cb_(done_cb) {
+  }
+
+ protected:
+  virtual void RunImpl(bool nonce_is_valid_and_unique,
+                       InsertStatus nonce_error) OVERRIDE {
+    DVLOG(1) << "Using client nonce, unique: " << nonce_is_valid_and_unique
+             << " nonce_error: " << nonce_error;
+    result_->info.unique = nonce_is_valid_and_unique;
+    if (!nonce_is_valid_and_unique) {
+      HandshakeFailureReason client_nonce_error;
+      switch (nonce_error) {
+        case NONCE_INVALID_FAILURE:
+          client_nonce_error = CLIENT_NONCE_INVALID_FAILURE;
+          break;
+        case NONCE_NOT_UNIQUE_FAILURE:
+          client_nonce_error = CLIENT_NONCE_NOT_UNIQUE_FAILURE;
+          break;
+        case NONCE_INVALID_ORBIT_FAILURE:
+          client_nonce_error = CLIENT_NONCE_INVALID_ORBIT_FAILURE;
+          break;
+        case NONCE_INVALID_TIME_FAILURE:
+          client_nonce_error = CLIENT_NONCE_INVALID_TIME_FAILURE;
+          break;
+        case STRIKE_REGISTER_TIMEOUT:
+          client_nonce_error = CLIENT_NONCE_STRIKE_REGISTER_TIMEOUT;
+          break;
+        case STRIKE_REGISTER_FAILURE:
+          client_nonce_error = CLIENT_NONCE_STRIKE_REGISTER_FAILURE;
+          break;
+        case NONCE_UNKNOWN_FAILURE:
+          client_nonce_error = CLIENT_NONCE_UNKNOWN_FAILURE;
+          break;
+        case NONCE_OK:
+        default:
+          LOG(DFATAL) << "Unexpected client nonce error: " << nonce_error;
+          client_nonce_error = CLIENT_NONCE_UNKNOWN_FAILURE;
+          break;
+      }
+      result_->info.reject_reasons.push_back(client_nonce_error);
+    }
+    done_cb_->Run(result_);
+  }
+
+ private:
+  ValidateClientHelloResultCallback::Result* result_;
+  ValidateClientHelloResultCallback* done_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(VerifyNonceIsValidAndUniqueCallback);
+};
+
+// static
+const char QuicCryptoServerConfig::TESTING[] = "secret string for testing";
+
+PrimaryConfigChangedCallback::PrimaryConfigChangedCallback() {
+}
+
+PrimaryConfigChangedCallback::~PrimaryConfigChangedCallback() {
+}
+
+ValidateClientHelloResultCallback::ValidateClientHelloResultCallback() {
+}
+
+ValidateClientHelloResultCallback::~ValidateClientHelloResultCallback() {
+}
+
+void ValidateClientHelloResultCallback::Run(const Result* result) {
+  RunImpl(result->client_hello, *result);
+  delete result;
+  delete this;
+}
+
+QuicCryptoServerConfig::ConfigOptions::ConfigOptions()
+    : expiry_time(QuicWallTime::Zero()),
+      channel_id_enabled(false),
+      p256(false) {}
+
+QuicCryptoServerConfig::QuicCryptoServerConfig(
+    StringPiece source_address_token_secret,
+    QuicRandom* rand)
+    : replay_protection_(true),
+      configs_lock_(),
+      primary_config_(nullptr),
+      next_config_promotion_time_(QuicWallTime::Zero()),
+      server_nonce_strike_register_lock_(),
+      strike_register_no_startup_period_(false),
+      strike_register_max_entries_(1 << 10),
+      strike_register_window_secs_(600),
+      source_address_token_future_secs_(3600),
+      source_address_token_lifetime_secs_(86400),
+      server_nonce_strike_register_max_entries_(1 << 10),
+      server_nonce_strike_register_window_secs_(120) {
+  default_source_address_token_boxer_.SetKey(
+      DeriveSourceAddressTokenKey(source_address_token_secret));
+
+  // Generate a random key and orbit for server nonces.
+  rand->RandBytes(server_nonce_orbit_, sizeof(server_nonce_orbit_));
+  const size_t key_size = server_nonce_boxer_.GetKeySize();
+  scoped_ptr<uint8[]> key_bytes(new uint8[key_size]);
+  rand->RandBytes(key_bytes.get(), key_size);
+
+  server_nonce_boxer_.SetKey(
+      StringPiece(reinterpret_cast<char*>(key_bytes.get()), key_size));
+}
+
+QuicCryptoServerConfig::~QuicCryptoServerConfig() {
+  primary_config_ = nullptr;
+}
+
+// static
+QuicServerConfigProtobuf* QuicCryptoServerConfig::GenerateConfig(
+    QuicRandom* rand,
+    const QuicClock* clock,
+    const ConfigOptions& options) {
+  CryptoHandshakeMessage msg;
+
+  const string curve25519_private_key =
+      Curve25519KeyExchange::NewPrivateKey(rand);
+  scoped_ptr<Curve25519KeyExchange> curve25519(
+      Curve25519KeyExchange::New(curve25519_private_key));
+  StringPiece curve25519_public_value = curve25519->public_value();
+
+  string encoded_public_values;
+  // First three bytes encode the length of the public value.
+  encoded_public_values.push_back(curve25519_public_value.size());
+  encoded_public_values.push_back(curve25519_public_value.size() >> 8);
+  encoded_public_values.push_back(curve25519_public_value.size() >> 16);
+  encoded_public_values.append(curve25519_public_value.data(),
+                               curve25519_public_value.size());
+
+  string p256_private_key;
+  if (options.p256) {
+    p256_private_key = P256KeyExchange::NewPrivateKey();
+    scoped_ptr<P256KeyExchange> p256(P256KeyExchange::New(p256_private_key));
+    StringPiece p256_public_value = p256->public_value();
+
+    encoded_public_values.push_back(p256_public_value.size());
+    encoded_public_values.push_back(p256_public_value.size() >> 8);
+    encoded_public_values.push_back(p256_public_value.size() >> 16);
+    encoded_public_values.append(p256_public_value.data(),
+                                 p256_public_value.size());
+  }
+
+  msg.set_tag(kSCFG);
+  if (options.p256) {
+    msg.SetTaglist(kKEXS, kC255, kP256, 0);
+  } else {
+    msg.SetTaglist(kKEXS, kC255, 0);
+  }
+  if (ChaCha20Poly1305Encrypter::IsSupported()) {
+    msg.SetTaglist(kAEAD, kAESG, kCC12, 0);
+  } else {
+    msg.SetTaglist(kAEAD, kAESG, 0);
+  }
+  msg.SetStringPiece(kPUBS, encoded_public_values);
+
+  if (options.expiry_time.IsZero()) {
+    const QuicWallTime now = clock->WallNow();
+    const QuicWallTime expiry = now.Add(QuicTime::Delta::FromSeconds(
+        60 * 60 * 24 * 180 /* 180 days, ~six months */));
+    const uint64 expiry_seconds = expiry.ToUNIXSeconds();
+    msg.SetValue(kEXPY, expiry_seconds);
+  } else {
+    msg.SetValue(kEXPY, options.expiry_time.ToUNIXSeconds());
+  }
+
+  char orbit_bytes[kOrbitSize];
+  if (options.orbit.size() == sizeof(orbit_bytes)) {
+    memcpy(orbit_bytes, options.orbit.data(), sizeof(orbit_bytes));
+  } else {
+    DCHECK(options.orbit.empty());
+    rand->RandBytes(orbit_bytes, sizeof(orbit_bytes));
+  }
+  msg.SetStringPiece(kORBT, StringPiece(orbit_bytes, sizeof(orbit_bytes)));
+
+  if (options.channel_id_enabled) {
+    msg.SetTaglist(kPDMD, kCHID, 0);
+  }
+
+  if (options.id.empty()) {
+    // We need to ensure that the SCID changes whenever the server config does
+    // thus we make it a hash of the rest of the server config.
+    scoped_ptr<QuicData> serialized(
+        CryptoFramer::ConstructHandshakeMessage(msg));
+    scoped_ptr<SecureHash> hash(SecureHash::Create(SecureHash::SHA256));
+    hash->Update(serialized->data(), serialized->length());
+
+    char scid_bytes[16];
+    hash->Finish(scid_bytes, sizeof(scid_bytes));
+    msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes)));
+  } else {
+    msg.SetStringPiece(kSCID, options.id);
+  }
+  // Don't put new tags below this point. The SCID generation should hash over
+  // everything but itself and so extra tags should be added prior to the
+  // preceeding if block.
+
+  scoped_ptr<QuicData> serialized(CryptoFramer::ConstructHandshakeMessage(msg));
+
+  scoped_ptr<QuicServerConfigProtobuf> config(new QuicServerConfigProtobuf);
+  config->set_config(serialized->AsStringPiece());
+  QuicServerConfigProtobuf::PrivateKey* curve25519_key = config->add_key();
+  curve25519_key->set_tag(kC255);
+  curve25519_key->set_private_key(curve25519_private_key);
+
+  if (options.p256) {
+    QuicServerConfigProtobuf::PrivateKey* p256_key = config->add_key();
+    p256_key->set_tag(kP256);
+    p256_key->set_private_key(p256_private_key);
+  }
+
+  return config.release();
+}
+
+CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig(
+    QuicServerConfigProtobuf* protobuf,
+    const QuicWallTime now) {
+  scoped_ptr<CryptoHandshakeMessage> msg(
+      CryptoFramer::ParseMessage(protobuf->config()));
+
+  if (!msg.get()) {
+    LOG(WARNING) << "Failed to parse server config message";
+    return nullptr;
+  }
+
+  scoped_refptr<Config> config(ParseConfigProtobuf(protobuf));
+  if (!config.get()) {
+    LOG(WARNING) << "Failed to parse server config message";
+    return nullptr;
+  }
+
+  {
+    base::AutoLock locked(configs_lock_);
+    if (configs_.find(config->id) != configs_.end()) {
+      LOG(WARNING) << "Failed to add config because another with the same "
+                      "server config id already exists: "
+                   << base::HexEncode(config->id.data(), config->id.size());
+      return nullptr;
+    }
+
+    configs_[config->id] = config;
+    SelectNewPrimaryConfig(now);
+    DCHECK(primary_config_.get());
+    DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_);
+  }
+
+  return msg.release();
+}
+
+CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig(
+    QuicRandom* rand,
+    const QuicClock* clock,
+    const ConfigOptions& options) {
+  scoped_ptr<QuicServerConfigProtobuf> config(
+      GenerateConfig(rand, clock, options));
+  return AddConfig(config.get(), clock->WallNow());
+}
+
+bool QuicCryptoServerConfig::SetConfigs(
+    const vector<QuicServerConfigProtobuf*>& protobufs,
+    const QuicWallTime now) {
+  vector<scoped_refptr<Config> > parsed_configs;
+  bool ok = true;
+
+  for (vector<QuicServerConfigProtobuf*>::const_iterator i = protobufs.begin();
+       i != protobufs.end(); ++i) {
+    scoped_refptr<Config> config(ParseConfigProtobuf(*i));
+    if (!config.get()) {
+      ok = false;
+      break;
+    }
+
+    parsed_configs.push_back(config);
+  }
+
+  if (parsed_configs.empty()) {
+    LOG(WARNING) << "New config list is empty.";
+    ok = false;
+  }
+
+  if (!ok) {
+    LOG(WARNING) << "Rejecting QUIC configs because of above errors";
+  } else {
+    VLOG(1) << "Updating configs:";
+
+    base::AutoLock locked(configs_lock_);
+    ConfigMap new_configs;
+
+    for (vector<scoped_refptr<Config> >::const_iterator i =
+             parsed_configs.begin();
+         i != parsed_configs.end(); ++i) {
+      scoped_refptr<Config> config = *i;
+
+      ConfigMap::iterator it = configs_.find(config->id);
+      if (it != configs_.end()) {
+        VLOG(1)
+            << "Keeping scid: " << base::HexEncode(
+                config->id.data(), config->id.size())
+            << " orbit: " << base::HexEncode(
+                reinterpret_cast<const char *>(config->orbit), kOrbitSize)
+            << " new primary_time " << config->primary_time.ToUNIXSeconds()
+            << " old primary_time " << it->second->primary_time.ToUNIXSeconds()
+            << " new priority " << config->priority
+            << " old priority " << it->second->priority;
+        // Update primary_time and priority.
+        it->second->primary_time = config->primary_time;
+        it->second->priority = config->priority;
+        new_configs.insert(*it);
+      } else {
+        VLOG(1) << "Adding scid: " << base::HexEncode(
+                    config->id.data(), config->id.size())
+                << " orbit: " << base::HexEncode(
+                    reinterpret_cast<const char *>(config->orbit), kOrbitSize)
+                << " primary_time " << config->primary_time.ToUNIXSeconds()
+                << " priority " << config->priority;
+        new_configs.insert(make_pair(config->id, config));
+      }
+    }
+
+    configs_.swap(new_configs);
+    SelectNewPrimaryConfig(now);
+    DCHECK(primary_config_.get());
+    DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_);
+  }
+
+  return ok;
+}
+
+void QuicCryptoServerConfig::GetConfigIds(vector<string>* scids) const {
+  base::AutoLock locked(configs_lock_);
+  for (ConfigMap::const_iterator it = configs_.begin();
+       it != configs_.end(); ++it) {
+    scids->push_back(it->first);
+  }
+}
+
+void QuicCryptoServerConfig::ValidateClientHello(
+    const CryptoHandshakeMessage& client_hello,
+    IPEndPoint client_ip,
+    const QuicClock* clock,
+    ValidateClientHelloResultCallback* done_cb) const {
+  const QuicWallTime now(clock->WallNow());
+
+  ValidateClientHelloResultCallback::Result* result =
+      new ValidateClientHelloResultCallback::Result(
+          client_hello, client_ip, now);
+
+  StringPiece requested_scid;
+  client_hello.GetStringPiece(kSCID, &requested_scid);
+
+  uint8 primary_orbit[kOrbitSize];
+  scoped_refptr<Config> requested_config;
+  {
+    base::AutoLock locked(configs_lock_);
+
+    if (!primary_config_.get()) {
+      result->error_code = QUIC_CRYPTO_INTERNAL_ERROR;
+      result->error_details = "No configurations loaded";
+    } else {
+      if (!next_config_promotion_time_.IsZero() &&
+          next_config_promotion_time_.IsAfter(now)) {
+        SelectNewPrimaryConfig(now);
+        DCHECK(primary_config_.get());
+        DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_);
+      }
+
+      memcpy(primary_orbit, primary_config_->orbit, sizeof(primary_orbit));
+    }
+
+    requested_config = GetConfigWithScid(requested_scid);
+  }
+
+  if (result->error_code == QUIC_NO_ERROR) {
+    EvaluateClientHello(primary_orbit, requested_config, result, done_cb);
+  } else {
+    done_cb->Run(result);
+  }
+}
+
+QuicErrorCode QuicCryptoServerConfig::ProcessClientHello(
+    const ValidateClientHelloResultCallback::Result& validate_chlo_result,
+    QuicConnectionId connection_id,
+    IPEndPoint client_address,
+    QuicVersion version,
+    const QuicVersionVector& supported_versions,
+    const QuicClock* clock,
+    QuicRandom* rand,
+    QuicCryptoNegotiatedParameters *params,
+    CryptoHandshakeMessage* out,
+    string* error_details) const {
+  DCHECK(error_details);
+
+  const CryptoHandshakeMessage& client_hello =
+      validate_chlo_result.client_hello;
+  const ClientHelloInfo& info = validate_chlo_result.info;
+
+  // If the client's preferred version is not the version we are currently
+  // speaking, then the client went through a version negotiation.  In this
+  // case, we need to make sure that we actually do not support this version
+  // and that it wasn't a downgrade attack.
+  QuicTag client_version_tag;
+  if (client_hello.GetUint32(kVER, &client_version_tag) != QUIC_NO_ERROR) {
+    *error_details = "client hello missing version list";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+  QuicVersion client_version = QuicTagToQuicVersion(client_version_tag);
+  if (client_version != version) {
+    // Just because client_version is a valid version enum doesn't mean that
+    // this server actually supports that version, so we check to see if
+    // it's actually in the supported versions list.
+    for (size_t i = 0; i < supported_versions.size(); ++i) {
+      if (client_version == supported_versions[i]) {
+        *error_details = "Downgrade attack detected";
+        return QUIC_VERSION_NEGOTIATION_MISMATCH;
+      }
+    }
+  }
+
+  StringPiece requested_scid;
+  client_hello.GetStringPiece(kSCID, &requested_scid);
+  const QuicWallTime now(clock->WallNow());
+
+  scoped_refptr<Config> requested_config;
+  scoped_refptr<Config> primary_config;
+  {
+    base::AutoLock locked(configs_lock_);
+
+    if (!primary_config_.get()) {
+      *error_details = "No configurations loaded";
+      return QUIC_CRYPTO_INTERNAL_ERROR;
+    }
+
+    if (!next_config_promotion_time_.IsZero() &&
+        next_config_promotion_time_.IsAfter(now)) {
+      SelectNewPrimaryConfig(now);
+      DCHECK(primary_config_.get());
+      DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_);
+    }
+
+    // We'll use the config that the client requested in order to do
+    // key-agreement. Otherwise we'll give it a copy of |primary_config_|
+    // to use.
+    primary_config = primary_config_;
+
+    requested_config = GetConfigWithScid(requested_scid);
+  }
+
+  if (validate_chlo_result.error_code != QUIC_NO_ERROR) {
+    *error_details = validate_chlo_result.error_details;
+    return validate_chlo_result.error_code;
+  }
+
+  out->Clear();
+
+  if (!info.valid_source_address_token ||
+      !info.client_nonce_well_formed ||
+      !info.unique ||
+      !requested_config.get()) {
+    BuildRejection(
+        *primary_config.get(), client_hello, info, rand, params, out);
+    return QUIC_NO_ERROR;
+  }
+
+  const QuicTag* their_aeads;
+  const QuicTag* their_key_exchanges;
+  size_t num_their_aeads, num_their_key_exchanges;
+  if (client_hello.GetTaglist(kAEAD, &their_aeads,
+                              &num_their_aeads) != QUIC_NO_ERROR ||
+      client_hello.GetTaglist(kKEXS, &their_key_exchanges,
+                              &num_their_key_exchanges) != QUIC_NO_ERROR ||
+      num_their_aeads != 1 ||
+      num_their_key_exchanges != 1) {
+    *error_details = "Missing or invalid AEAD or KEXS";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  size_t key_exchange_index;
+  if (!QuicUtils::FindMutualTag(requested_config->aead, their_aeads,
+                                num_their_aeads, QuicUtils::LOCAL_PRIORITY,
+                                &params->aead, nullptr) ||
+      !QuicUtils::FindMutualTag(
+          requested_config->kexs, their_key_exchanges, num_their_key_exchanges,
+          QuicUtils::LOCAL_PRIORITY, &params->key_exchange,
+          &key_exchange_index)) {
+    *error_details = "Unsupported AEAD or KEXS";
+    return QUIC_CRYPTO_NO_SUPPORT;
+  }
+
+  StringPiece public_value;
+  if (!client_hello.GetStringPiece(kPUBS, &public_value)) {
+    *error_details = "Missing public value";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  const KeyExchange* key_exchange =
+      requested_config->key_exchanges[key_exchange_index];
+  if (!key_exchange->CalculateSharedKey(public_value,
+                                        &params->initial_premaster_secret)) {
+    *error_details = "Invalid public value";
+    return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+  }
+
+  if (!info.sni.empty()) {
+    scoped_ptr<char[]> sni_tmp(new char[info.sni.length() + 1]);
+    memcpy(sni_tmp.get(), info.sni.data(), info.sni.length());
+    sni_tmp[info.sni.length()] = 0;
+    params->sni = CryptoUtils::NormalizeHostname(sni_tmp.get());
+  }
+
+  string hkdf_suffix;
+  const QuicData& client_hello_serialized = client_hello.GetSerialized();
+  hkdf_suffix.reserve(sizeof(connection_id) + client_hello_serialized.length() +
+                      requested_config->serialized.size());
+  hkdf_suffix.append(reinterpret_cast<char*>(&connection_id),
+                     sizeof(connection_id));
+  hkdf_suffix.append(client_hello_serialized.data(),
+                     client_hello_serialized.length());
+  hkdf_suffix.append(requested_config->serialized);
+
+  StringPiece cetv_ciphertext;
+  if (requested_config->channel_id_enabled &&
+      client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) {
+    CryptoHandshakeMessage client_hello_copy(client_hello);
+    client_hello_copy.Erase(kCETV);
+    client_hello_copy.Erase(kPAD);
+
+    const QuicData& client_hello_serialized = client_hello_copy.GetSerialized();
+    string hkdf_input;
+    hkdf_input.append(QuicCryptoConfig::kCETVLabel,
+                      strlen(QuicCryptoConfig::kCETVLabel) + 1);
+    hkdf_input.append(reinterpret_cast<char*>(&connection_id),
+                      sizeof(connection_id));
+    hkdf_input.append(client_hello_serialized.data(),
+                      client_hello_serialized.length());
+    hkdf_input.append(requested_config->serialized);
+
+    CrypterPair crypters;
+    if (!CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead,
+                                 info.client_nonce, info.server_nonce,
+                                 hkdf_input, CryptoUtils::SERVER, &crypters,
+                                 nullptr /* subkey secret */)) {
+      *error_details = "Symmetric key setup failed";
+      return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+    }
+
+    scoped_ptr<QuicData> cetv_plaintext(crypters.decrypter->DecryptPacket(
+        0 /* sequence number */, StringPiece() /* associated data */,
+        cetv_ciphertext));
+    if (!cetv_plaintext.get()) {
+      *error_details = "CETV decryption failure";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    scoped_ptr<CryptoHandshakeMessage> cetv(CryptoFramer::ParseMessage(
+        cetv_plaintext->AsStringPiece()));
+    if (!cetv.get()) {
+      *error_details = "CETV parse error";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+
+    StringPiece key, signature;
+    if (cetv->GetStringPiece(kCIDK, &key) &&
+        cetv->GetStringPiece(kCIDS, &signature)) {
+      if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) {
+        *error_details = "ChannelID signature failure";
+        return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+      }
+
+      params->channel_id = key.as_string();
+    }
+  }
+
+  string hkdf_input;
+  size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
+  hkdf_input.reserve(label_len + hkdf_suffix.size());
+  hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len);
+  hkdf_input.append(hkdf_suffix);
+
+  if (!CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead,
+                               info.client_nonce, info.server_nonce, hkdf_input,
+                               CryptoUtils::SERVER,
+                               &params->initial_crypters,
+                               nullptr /* subkey secret */)) {
+    *error_details = "Symmetric key setup failed";
+    return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+  }
+
+  string forward_secure_public_value;
+  if (ephemeral_key_source_.get()) {
+    params->forward_secure_premaster_secret =
+        ephemeral_key_source_->CalculateForwardSecureKey(
+            key_exchange, rand, clock->ApproximateNow(), public_value,
+            &forward_secure_public_value);
+  } else {
+    scoped_ptr<KeyExchange> forward_secure_key_exchange(
+        key_exchange->NewKeyPair(rand));
+    forward_secure_public_value =
+        forward_secure_key_exchange->public_value().as_string();
+    if (!forward_secure_key_exchange->CalculateSharedKey(
+            public_value, &params->forward_secure_premaster_secret)) {
+      *error_details = "Invalid public value";
+      return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+    }
+  }
+
+  string forward_secure_hkdf_input;
+  label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1;
+  forward_secure_hkdf_input.reserve(label_len + hkdf_suffix.size());
+  forward_secure_hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel,
+                                   label_len);
+  forward_secure_hkdf_input.append(hkdf_suffix);
+
+  if (!CryptoUtils::DeriveKeys(
+           params->forward_secure_premaster_secret, params->aead,
+           info.client_nonce, info.server_nonce, forward_secure_hkdf_input,
+           CryptoUtils::SERVER, &params->forward_secure_crypters,
+           &params->subkey_secret)) {
+    *error_details = "Symmetric key setup failed";
+    return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED;
+  }
+
+  out->set_tag(kSHLO);
+  QuicTagVector supported_version_tags;
+  for (size_t i = 0; i < supported_versions.size(); ++i) {
+    supported_version_tags.push_back
+        (QuicVersionToQuicTag(supported_versions[i]));
+  }
+  out->SetVector(kVER, supported_version_tags);
+  out->SetStringPiece(
+      kSourceAddressTokenTag,
+      NewSourceAddressToken(*requested_config.get(),
+                            client_address,
+                            rand,
+                            info.now,
+                            nullptr));
+  QuicSocketAddressCoder address_coder(client_address);
+  out->SetStringPiece(kCADR, address_coder.Encode());
+  out->SetStringPiece(kPUBS, forward_secure_public_value);
+
+  return QUIC_NO_ERROR;
+}
+
+scoped_refptr<QuicCryptoServerConfig::Config>
+QuicCryptoServerConfig::GetConfigWithScid(StringPiece requested_scid) const {
+  // In Chromium, we will dead lock if the lock is held by the current thread.
+  // Chromium doesn't have AssertReaderHeld API call.
+  // configs_lock_.AssertReaderHeld();
+
+  if (!requested_scid.empty()) {
+    ConfigMap::const_iterator it = configs_.find(requested_scid.as_string());
+    if (it != configs_.end()) {
+      // We'll use the config that the client requested in order to do
+      // key-agreement.
+      return scoped_refptr<Config>(it->second);
+    }
+  }
+
+  return scoped_refptr<Config>();
+}
+
+// ConfigPrimaryTimeLessThan is a comparator that implements "less than" for
+// Config's based on their primary_time.
+// static
+bool QuicCryptoServerConfig::ConfigPrimaryTimeLessThan(
+    const scoped_refptr<Config>& a,
+    const scoped_refptr<Config>& b) {
+  if (a->primary_time.IsBefore(b->primary_time) ||
+      b->primary_time.IsBefore(a->primary_time)) {
+    // Primary times differ.
+    return a->primary_time.IsBefore(b->primary_time);
+  } else if (a->priority != b->priority) {
+    // Primary times are equal, sort backwards by priority.
+    return a->priority < b->priority;
+  } else {
+    // Primary times and priorities are equal, sort by config id.
+    return a->id < b->id;
+  }
+}
+
+void QuicCryptoServerConfig::SelectNewPrimaryConfig(
+    const QuicWallTime now) const {
+  vector<scoped_refptr<Config> > configs;
+  configs.reserve(configs_.size());
+
+  for (ConfigMap::const_iterator it = configs_.begin();
+       it != configs_.end(); ++it) {
+    // TODO(avd) Exclude expired configs?
+    configs.push_back(it->second);
+  }
+
+  if (configs.empty()) {
+    if (primary_config_.get()) {
+      LOG(DFATAL) << "No valid QUIC server config. Keeping the current config.";
+    } else {
+      LOG(DFATAL) << "No valid QUIC server config.";
+    }
+    return;
+  }
+
+  sort(configs.begin(), configs.end(), ConfigPrimaryTimeLessThan);
+
+  Config* best_candidate = configs[0].get();
+
+  for (size_t i = 0; i < configs.size(); ++i) {
+    const scoped_refptr<Config> config(configs[i]);
+    if (!config->primary_time.IsAfter(now)) {
+      if (config->primary_time.IsAfter(best_candidate->primary_time)) {
+        best_candidate = config.get();
+      }
+      continue;
+    }
+
+    // This is the first config with a primary_time in the future. Thus the
+    // previous Config should be the primary and this one should determine the
+    // next_config_promotion_time_.
+    scoped_refptr<Config> new_primary(best_candidate);
+    if (i == 0) {
+      // We need the primary_time of the next config.
+      if (configs.size() > 1) {
+        next_config_promotion_time_ = configs[1]->primary_time;
+      } else {
+        next_config_promotion_time_ = QuicWallTime::Zero();
+      }
+    } else {
+      next_config_promotion_time_ = config->primary_time;
+    }
+
+    if (primary_config_.get()) {
+      primary_config_->is_primary = false;
+    }
+    primary_config_ = new_primary;
+    new_primary->is_primary = true;
+    DVLOG(1) << "New primary config.  orbit: "
+             << base::HexEncode(
+                 reinterpret_cast<const char*>(primary_config_->orbit),
+                 kOrbitSize);
+    if (primary_config_changed_cb_.get() != nullptr) {
+      primary_config_changed_cb_->Run(primary_config_->id);
+    }
+
+    return;
+  }
+
+  // All config's primary times are in the past. We should make the most recent
+  // and highest priority candidate primary.
+  scoped_refptr<Config> new_primary(best_candidate);
+  if (primary_config_.get()) {
+    primary_config_->is_primary = false;
+  }
+  primary_config_ = new_primary;
+  new_primary->is_primary = true;
+  DVLOG(1) << "New primary config.  orbit: "
+           << base::HexEncode(
+               reinterpret_cast<const char*>(primary_config_->orbit),
+               kOrbitSize)
+           << " scid: " << base::HexEncode(primary_config_->id.data(),
+                                           primary_config_->id.size());
+  next_config_promotion_time_ = QuicWallTime::Zero();
+  if (primary_config_changed_cb_.get() != nullptr) {
+    primary_config_changed_cb_->Run(primary_config_->id);
+  }
+}
+
+void QuicCryptoServerConfig::EvaluateClientHello(
+    const uint8* primary_orbit,
+    scoped_refptr<Config> requested_config,
+    ValidateClientHelloResultCallback::Result* client_hello_state,
+    ValidateClientHelloResultCallback* done_cb) const {
+  ValidateClientHelloHelper helper(client_hello_state, done_cb);
+
+  const CryptoHandshakeMessage& client_hello =
+      client_hello_state->client_hello;
+  ClientHelloInfo* info = &(client_hello_state->info);
+
+  if (client_hello.size() < kClientHelloMinimumSize) {
+    helper.ValidationComplete(QUIC_CRYPTO_INVALID_VALUE_LENGTH,
+                              "Client hello too small");
+    return;
+  }
+
+  if (client_hello.GetStringPiece(kSNI, &info->sni) &&
+      !CryptoUtils::IsValidSNI(info->sni)) {
+    helper.ValidationComplete(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
+                              "Invalid SNI name");
+    return;
+  }
+
+  client_hello.GetStringPiece(kUAID, &info->user_agent_id);
+
+  if (!requested_config.get()) {
+    StringPiece requested_scid;
+    if (client_hello.GetStringPiece(kSCID, &requested_scid)) {
+      info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE);
+    } else {
+      info->reject_reasons.push_back(SERVER_CONFIG_INCHOATE_HELLO_FAILURE);
+    }
+    // No server config with the requested ID.
+    helper.ValidationComplete(QUIC_NO_ERROR, "");
+    return;
+  }
+
+  HandshakeFailureReason source_address_token_error;
+  StringPiece srct;
+  if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct)) {
+    source_address_token_error = ValidateSourceAddressToken(
+        *requested_config.get(), srct, info->client_ip, info->now);
+    info->valid_source_address_token =
+        (source_address_token_error == HANDSHAKE_OK);
+  } else {
+    source_address_token_error = SOURCE_ADDRESS_TOKEN_INVALID_FAILURE;
+  }
+
+  bool found_error = false;
+  if (source_address_token_error != HANDSHAKE_OK) {
+    info->reject_reasons.push_back(source_address_token_error);
+    // No valid source address token.
+    if (FLAGS_use_early_return_when_verifying_chlo) {
+      helper.ValidationComplete(QUIC_NO_ERROR, "");
+      return;
+    }
+    found_error = true;
+  }
+
+  if (client_hello.GetStringPiece(kNONC, &info->client_nonce) &&
+      info->client_nonce.size() == kNonceSize) {
+    info->client_nonce_well_formed = true;
+  } else {
+    info->reject_reasons.push_back(CLIENT_NONCE_INVALID_FAILURE);
+    // Invalid client nonce.
+    DVLOG(1) << "Invalid client nonce.";
+    if (FLAGS_use_early_return_when_verifying_chlo) {
+      helper.ValidationComplete(QUIC_NO_ERROR, "");
+      return;
+    }
+    found_error = true;
+  }
+
+  if (!replay_protection_) {
+    if (!found_error) {
+      info->unique = true;
+    }
+    DVLOG(1) << "No replay protection.";
+    helper.ValidationComplete(QUIC_NO_ERROR, "");
+    return;
+  }
+
+  client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce);
+  if (!info->server_nonce.empty()) {
+    // If the server nonce is present, use it to establish uniqueness.
+    HandshakeFailureReason server_nonce_error =
+        ValidateServerNonce(info->server_nonce, info->now);
+    if (server_nonce_error == HANDSHAKE_OK) {
+      info->unique = true;
+    } else {
+      info->reject_reasons.push_back(server_nonce_error);
+      info->unique = false;
+    }
+    DVLOG(1) << "Using server nonce, unique: " << info->unique;
+    helper.ValidationComplete(QUIC_NO_ERROR, "");
+    return;
+  }
+
+  // We want to contact strike register only if there are no errors because it
+  // is a RPC call and is expensive.
+  if (found_error) {
+    helper.ValidationComplete(QUIC_NO_ERROR, "");
+    return;
+  }
+
+  // Use the client nonce to establish uniqueness.
+  StrikeRegisterClient* strike_register_client;
+  {
+    base::AutoLock locked(strike_register_client_lock_);
+
+    if (strike_register_client_.get() == nullptr) {
+      strike_register_client_.reset(new LocalStrikeRegisterClient(
+          strike_register_max_entries_,
+          static_cast<uint32>(info->now.ToUNIXSeconds()),
+          strike_register_window_secs_,
+          primary_orbit,
+          strike_register_no_startup_period_ ?
+          StrikeRegister::NO_STARTUP_PERIOD_NEEDED :
+          StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+    }
+    strike_register_client = strike_register_client_.get();
+  }
+
+  strike_register_client->VerifyNonceIsValidAndUnique(
+      info->client_nonce,
+      info->now,
+      new VerifyNonceIsValidAndUniqueCallback(client_hello_state, done_cb));
+  helper.StartedAsyncCallback();
+}
+
+bool QuicCryptoServerConfig::BuildServerConfigUpdateMessage(
+    const IPEndPoint& client_ip,
+    const QuicClock* clock,
+    QuicRandom* rand,
+    const QuicCryptoNegotiatedParameters& params,
+    const CachedNetworkParameters* cached_network_params,
+    CryptoHandshakeMessage* out) const {
+  base::AutoLock locked(configs_lock_);
+  out->set_tag(kSCUP);
+  out->SetStringPiece(kSCFG, primary_config_->serialized);
+  out->SetStringPiece(kSourceAddressTokenTag,
+                      NewSourceAddressToken(*primary_config_.get(),
+                                            client_ip,
+                                            rand,
+                                            clock->WallNow(),
+                                            cached_network_params));
+
+  if (proof_source_ == nullptr) {
+    // Insecure QUIC, can send SCFG without proof.
+    return true;
+  }
+
+  const vector<string>* certs;
+  string signature;
+  if (!proof_source_->GetProof(params.sni, primary_config_->serialized,
+                               params.x509_ecdsa_supported, &certs,
+                               &signature)) {
+    DVLOG(1) << "Server: failed to get proof.";
+    return false;
+  }
+
+  const string compressed = CertCompressor::CompressChain(
+      *certs, params.client_common_set_hashes, params.client_cached_cert_hashes,
+      primary_config_->common_cert_sets);
+
+  out->SetStringPiece(kCertificateTag, compressed);
+  out->SetStringPiece(kPROF, signature);
+  return true;
+}
+
+void QuicCryptoServerConfig::BuildRejection(
+    const Config& config,
+    const CryptoHandshakeMessage& client_hello,
+    const ClientHelloInfo& info,
+    QuicRandom* rand,
+    QuicCryptoNegotiatedParameters *params,
+    CryptoHandshakeMessage* out) const {
+  out->set_tag(kREJ);
+  out->SetStringPiece(kSCFG, config.serialized);
+  out->SetStringPiece(kSourceAddressTokenTag,
+                      NewSourceAddressToken(
+                          config,
+                          info.client_ip,
+                          rand,
+                          info.now,
+                          nullptr));
+  if (replay_protection_) {
+    out->SetStringPiece(kServerNonceTag, NewServerNonce(rand, info.now));
+  }
+
+  if (FLAGS_send_quic_crypto_reject_reason) {
+    // Send client the reject reason for debugging purposes.
+    DCHECK_LT(0u, info.reject_reasons.size());
+    out->SetVector(kRREJ, info.reject_reasons);
+  }
+
+  // The client may have requested a certificate chain.
+  const QuicTag* their_proof_demands;
+  size_t num_their_proof_demands;
+
+  if (proof_source_.get() == nullptr ||
+      client_hello.GetTaglist(kPDMD, &their_proof_demands,
+                              &num_their_proof_demands) !=
+          QUIC_NO_ERROR) {
+    return;
+  }
+
+  bool x509_supported = false;
+  for (size_t i = 0; i < num_their_proof_demands; i++) {
+    switch (their_proof_demands[i]) {
+      case kX509:
+        x509_supported = true;
+        params->x509_ecdsa_supported = true;
+        break;
+      case kX59R:
+        x509_supported = true;
+        break;
+    }
+  }
+
+  if (!x509_supported) {
+    return;
+  }
+
+  const vector<string>* certs;
+  string signature;
+  if (!proof_source_->GetProof(info.sni.as_string(), config.serialized,
+                               params->x509_ecdsa_supported, &certs,
+                               &signature)) {
+    return;
+  }
+
+  StringPiece client_common_set_hashes;
+  if (client_hello.GetStringPiece(kCCS, &client_common_set_hashes)) {
+    params->client_common_set_hashes = client_common_set_hashes.as_string();
+  }
+
+  StringPiece client_cached_cert_hashes;
+  if (client_hello.GetStringPiece(kCCRT, &client_cached_cert_hashes)) {
+    params->client_cached_cert_hashes = client_cached_cert_hashes.as_string();
+  }
+
+  const string compressed = CertCompressor::CompressChain(
+      *certs, params->client_common_set_hashes,
+      params->client_cached_cert_hashes, config.common_cert_sets);
+
+  // kREJOverheadBytes is a very rough estimate of how much of a REJ
+  // message is taken up by things other than the certificates.
+  // STK: 56 bytes
+  // SNO: 56 bytes
+  // SCFG
+  //   SCID: 16 bytes
+  //   PUBS: 38 bytes
+  const size_t kREJOverheadBytes = 166;
+  // kMultiplier is the multiple of the CHLO message size that a REJ message
+  // must stay under when the client doesn't present a valid source-address
+  // token.
+  const size_t kMultiplier = 2;
+  // max_unverified_size is the number of bytes that the certificate chain
+  // and signature can consume before we will demand a valid source-address
+  // token.
+  const size_t max_unverified_size =
+      client_hello.size() * kMultiplier - kREJOverheadBytes;
+  COMPILE_ASSERT(kClientHelloMinimumSize * kMultiplier >= kREJOverheadBytes,
+                 overhead_calculation_may_underflow);
+  if (info.valid_source_address_token ||
+      signature.size() + compressed.size() < max_unverified_size) {
+    out->SetStringPiece(kCertificateTag, compressed);
+    out->SetStringPiece(kPROF, signature);
+  }
+}
+
+scoped_refptr<QuicCryptoServerConfig::Config>
+QuicCryptoServerConfig::ParseConfigProtobuf(
+    QuicServerConfigProtobuf* protobuf) {
+  scoped_ptr<CryptoHandshakeMessage> msg(
+      CryptoFramer::ParseMessage(protobuf->config()));
+
+  if (msg->tag() != kSCFG) {
+    LOG(WARNING) << "Server config message has tag " << msg->tag()
+                 << " expected " << kSCFG;
+    return nullptr;
+  }
+
+  scoped_refptr<Config> config(new Config);
+  config->serialized = protobuf->config();
+
+  if (!protobuf->has_source_address_token_secret_override()) {
+    // Use the default boxer.
+    config->source_address_token_boxer = &default_source_address_token_boxer_;
+  } else {
+    // Create override boxer instance.
+    CryptoSecretBoxer* boxer = new CryptoSecretBoxer;
+    boxer->SetKey(DeriveSourceAddressTokenKey(
+        protobuf->source_address_token_secret_override()));
+    config->source_address_token_boxer_storage.reset(boxer);
+    config->source_address_token_boxer = boxer;
+  }
+
+  if (protobuf->has_primary_time()) {
+    config->primary_time =
+        QuicWallTime::FromUNIXSeconds(protobuf->primary_time());
+  }
+
+  config->priority = protobuf->priority();
+
+  StringPiece scid;
+  if (!msg->GetStringPiece(kSCID, &scid)) {
+    LOG(WARNING) << "Server config message is missing SCID";
+    return nullptr;
+  }
+  config->id = scid.as_string();
+
+  const QuicTag* aead_tags;
+  size_t aead_len;
+  if (msg->GetTaglist(kAEAD, &aead_tags, &aead_len) != QUIC_NO_ERROR) {
+    LOG(WARNING) << "Server config message is missing AEAD";
+    return nullptr;
+  }
+  config->aead = vector<QuicTag>(aead_tags, aead_tags + aead_len);
+
+  const QuicTag* kexs_tags;
+  size_t kexs_len;
+  if (msg->GetTaglist(kKEXS, &kexs_tags, &kexs_len) != QUIC_NO_ERROR) {
+    LOG(WARNING) << "Server config message is missing KEXS";
+    return nullptr;
+  }
+
+  StringPiece orbit;
+  if (!msg->GetStringPiece(kORBT, &orbit)) {
+    LOG(WARNING) << "Server config message is missing ORBT";
+    return nullptr;
+  }
+
+  if (orbit.size() != kOrbitSize) {
+    LOG(WARNING) << "Orbit value in server config is the wrong length."
+                    " Got " << orbit.size() << " want " << kOrbitSize;
+    return nullptr;
+  }
+  COMPILE_ASSERT(sizeof(config->orbit) == kOrbitSize, orbit_incorrect_size);
+  memcpy(config->orbit, orbit.data(), sizeof(config->orbit));
+
+  {
+    StrikeRegisterClient* strike_register_client;
+    {
+      base::AutoLock locked(strike_register_client_lock_);
+      strike_register_client = strike_register_client_.get();
+    }
+
+    if (strike_register_client != nullptr &&
+        !strike_register_client->IsKnownOrbit(orbit)) {
+      LOG(WARNING)
+          << "Rejecting server config with orbit that the strike register "
+          "client doesn't know about.";
+      return nullptr;
+    }
+  }
+
+  if (kexs_len != protobuf->key_size()) {
+    LOG(WARNING) << "Server config has " << kexs_len
+                 << " key exchange methods configured, but "
+                 << protobuf->key_size() << " private keys";
+    return nullptr;
+  }
+
+  const QuicTag* proof_demand_tags;
+  size_t num_proof_demand_tags;
+  if (msg->GetTaglist(kPDMD, &proof_demand_tags, &num_proof_demand_tags) ==
+      QUIC_NO_ERROR) {
+    for (size_t i = 0; i < num_proof_demand_tags; i++) {
+      if (proof_demand_tags[i] == kCHID) {
+        config->channel_id_enabled = true;
+        break;
+      }
+    }
+  }
+
+  for (size_t i = 0; i < kexs_len; i++) {
+    const QuicTag tag = kexs_tags[i];
+    string private_key;
+
+    config->kexs.push_back(tag);
+
+    for (size_t j = 0; j < protobuf->key_size(); j++) {
+      const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i);
+      if (key.tag() == tag) {
+        private_key = key.private_key();
+        break;
+      }
+    }
+
+    if (private_key.empty()) {
+      LOG(WARNING) << "Server config contains key exchange method without "
+                      "corresponding private key: " << tag;
+      return nullptr;
+    }
+
+    scoped_ptr<KeyExchange> ka;
+    switch (tag) {
+      case kC255:
+        ka.reset(Curve25519KeyExchange::New(private_key));
+        if (!ka.get()) {
+          LOG(WARNING) << "Server config contained an invalid curve25519"
+                          " private key.";
+          return nullptr;
+        }
+        break;
+      case kP256:
+        ka.reset(P256KeyExchange::New(private_key));
+        if (!ka.get()) {
+          LOG(WARNING) << "Server config contained an invalid P-256"
+                          " private key.";
+          return nullptr;
+        }
+        break;
+      default:
+        LOG(WARNING) << "Server config message contains unknown key exchange "
+                        "method: " << tag;
+        return nullptr;
+    }
+
+    for (vector<KeyExchange*>::const_iterator i = config->key_exchanges.begin();
+         i != config->key_exchanges.end(); ++i) {
+      if ((*i)->tag() == tag) {
+        LOG(WARNING) << "Duplicate key exchange in config: " << tag;
+        return nullptr;
+      }
+    }
+
+    config->key_exchanges.push_back(ka.release());
+  }
+
+  return config;
+}
+
+void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) {
+  proof_source_.reset(proof_source);
+}
+
+void QuicCryptoServerConfig::SetEphemeralKeySource(
+    EphemeralKeySource* ephemeral_key_source) {
+  ephemeral_key_source_.reset(ephemeral_key_source);
+}
+
+void QuicCryptoServerConfig::SetStrikeRegisterClient(
+    StrikeRegisterClient* strike_register_client) {
+  base::AutoLock locker(strike_register_client_lock_);
+  DCHECK(!strike_register_client_.get());
+  strike_register_client_.reset(strike_register_client);
+}
+
+void QuicCryptoServerConfig::set_replay_protection(bool on) {
+  replay_protection_ = on;
+}
+
+void QuicCryptoServerConfig::set_strike_register_no_startup_period() {
+  base::AutoLock locker(strike_register_client_lock_);
+  DCHECK(!strike_register_client_.get());
+  strike_register_no_startup_period_ = true;
+}
+
+void QuicCryptoServerConfig::set_strike_register_max_entries(
+    uint32 max_entries) {
+  base::AutoLock locker(strike_register_client_lock_);
+  DCHECK(!strike_register_client_.get());
+  strike_register_max_entries_ = max_entries;
+}
+
+void QuicCryptoServerConfig::set_strike_register_window_secs(
+    uint32 window_secs) {
+  base::AutoLock locker(strike_register_client_lock_);
+  DCHECK(!strike_register_client_.get());
+  strike_register_window_secs_ = window_secs;
+}
+
+void QuicCryptoServerConfig::set_source_address_token_future_secs(
+    uint32 future_secs) {
+  source_address_token_future_secs_ = future_secs;
+}
+
+void QuicCryptoServerConfig::set_source_address_token_lifetime_secs(
+    uint32 lifetime_secs) {
+  source_address_token_lifetime_secs_ = lifetime_secs;
+}
+
+void QuicCryptoServerConfig::set_server_nonce_strike_register_max_entries(
+    uint32 max_entries) {
+  DCHECK(!server_nonce_strike_register_.get());
+  server_nonce_strike_register_max_entries_ = max_entries;
+}
+
+void QuicCryptoServerConfig::set_server_nonce_strike_register_window_secs(
+    uint32 window_secs) {
+  DCHECK(!server_nonce_strike_register_.get());
+  server_nonce_strike_register_window_secs_ = window_secs;
+}
+
+void QuicCryptoServerConfig::AcquirePrimaryConfigChangedCb(
+    PrimaryConfigChangedCallback* cb) {
+  base::AutoLock locked(configs_lock_);
+  primary_config_changed_cb_.reset(cb);
+}
+
+string QuicCryptoServerConfig::NewSourceAddressToken(
+    const Config& config,
+    const IPEndPoint& ip,
+    QuicRandom* rand,
+    QuicWallTime now,
+    const CachedNetworkParameters* cached_network_params) const {
+  IPAddressNumber ip_address = ip.address();
+  if (ip.GetSockAddrFamily() == AF_INET) {
+    ip_address = ConvertIPv4NumberToIPv6Number(ip_address);
+  }
+  SourceAddressToken source_address_token;
+  source_address_token.set_ip(IPAddressToPackedString(ip_address));
+  source_address_token.set_timestamp(now.ToUNIXSeconds());
+  if (cached_network_params != nullptr) {
+    source_address_token.set_cached_network_parameters(*cached_network_params);
+  }
+
+  return config.source_address_token_boxer->Box(
+      rand, source_address_token.SerializeAsString());
+}
+
+HandshakeFailureReason QuicCryptoServerConfig::ValidateSourceAddressToken(
+    const Config& config,
+    StringPiece token,
+    const IPEndPoint& ip,
+    QuicWallTime now) const {
+  string storage;
+  StringPiece plaintext;
+  if (!config.source_address_token_boxer->Unbox(token, &storage, &plaintext)) {
+    return SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE;
+  }
+
+  SourceAddressToken source_address_token;
+  if (!source_address_token.ParseFromArray(plaintext.data(),
+                                           plaintext.size())) {
+    return SOURCE_ADDRESS_TOKEN_PARSE_FAILURE;
+  }
+
+  IPAddressNumber ip_address = ip.address();
+  if (ip.GetSockAddrFamily() == AF_INET) {
+    ip_address = ConvertIPv4NumberToIPv6Number(ip_address);
+  }
+  if (source_address_token.ip() != IPAddressToPackedString(ip_address)) {
+    // It's for a different IP address.
+    return SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE;
+  }
+
+  const QuicWallTime timestamp(
+      QuicWallTime::FromUNIXSeconds(source_address_token.timestamp()));
+  const QuicTime::Delta delta(now.AbsoluteDifference(timestamp));
+
+  if (now.IsBefore(timestamp) &&
+      delta.ToSeconds() > source_address_token_future_secs_) {
+    return SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE;
+  }
+
+  if (now.IsAfter(timestamp) &&
+      delta.ToSeconds() > source_address_token_lifetime_secs_) {
+    return SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE;
+  }
+
+  return HANDSHAKE_OK;
+}
+
+// kServerNoncePlaintextSize is the number of bytes in an unencrypted server
+// nonce.
+static const size_t kServerNoncePlaintextSize =
+    4 /* timestamp */ + 20 /* random bytes */;
+
+string QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand,
+                                              QuicWallTime now) const {
+  const uint32 timestamp = static_cast<uint32>(now.ToUNIXSeconds());
+
+  uint8 server_nonce[kServerNoncePlaintextSize];
+  COMPILE_ASSERT(sizeof(server_nonce) > sizeof(timestamp), nonce_too_small);
+  server_nonce[0] = static_cast<uint8>(timestamp >> 24);
+  server_nonce[1] = static_cast<uint8>(timestamp >> 16);
+  server_nonce[2] = static_cast<uint8>(timestamp >> 8);
+  server_nonce[3] = static_cast<uint8>(timestamp);
+  rand->RandBytes(&server_nonce[sizeof(timestamp)],
+                  sizeof(server_nonce) - sizeof(timestamp));
+
+  return server_nonce_boxer_.Box(
+      rand,
+      StringPiece(reinterpret_cast<char*>(server_nonce), sizeof(server_nonce)));
+}
+
+HandshakeFailureReason QuicCryptoServerConfig::ValidateServerNonce(
+    StringPiece token,
+    QuicWallTime now) const {
+  string storage;
+  StringPiece plaintext;
+  if (!server_nonce_boxer_.Unbox(token, &storage, &plaintext)) {
+    return SERVER_NONCE_DECRYPTION_FAILURE;
+  }
+
+  // plaintext contains:
+  //   uint32 timestamp
+  //   uint8[20] random bytes
+
+  if (plaintext.size() != kServerNoncePlaintextSize) {
+    // This should never happen because the value decrypted correctly.
+    LOG(DFATAL) << "Seemingly valid server nonce had incorrect length.";
+    return SERVER_NONCE_INVALID_FAILURE;
+  }
+
+  uint8 server_nonce[32];
+  memcpy(server_nonce, plaintext.data(), 4);
+  memcpy(server_nonce + 4, server_nonce_orbit_, sizeof(server_nonce_orbit_));
+  memcpy(server_nonce + 4 + sizeof(server_nonce_orbit_), plaintext.data() + 4,
+         20);
+  COMPILE_ASSERT(4 + sizeof(server_nonce_orbit_) + 20 == sizeof(server_nonce),
+                 bad_nonce_buffer_length);
+
+  InsertStatus nonce_error;
+  {
+    base::AutoLock auto_lock(server_nonce_strike_register_lock_);
+    if (server_nonce_strike_register_.get() == nullptr) {
+      server_nonce_strike_register_.reset(new StrikeRegister(
+          server_nonce_strike_register_max_entries_,
+          static_cast<uint32>(now.ToUNIXSeconds()),
+          server_nonce_strike_register_window_secs_, server_nonce_orbit_,
+          StrikeRegister::NO_STARTUP_PERIOD_NEEDED));
+    }
+    nonce_error = server_nonce_strike_register_->Insert(
+        server_nonce, static_cast<uint32>(now.ToUNIXSeconds()));
+  }
+
+  switch (nonce_error) {
+    case NONCE_OK:
+      return HANDSHAKE_OK;
+    case NONCE_INVALID_FAILURE:
+    case NONCE_INVALID_ORBIT_FAILURE:
+      return SERVER_NONCE_INVALID_FAILURE;
+    case NONCE_NOT_UNIQUE_FAILURE:
+      return SERVER_NONCE_NOT_UNIQUE_FAILURE;
+    case NONCE_INVALID_TIME_FAILURE:
+      return SERVER_NONCE_INVALID_TIME_FAILURE;
+    case NONCE_UNKNOWN_FAILURE:
+    case STRIKE_REGISTER_TIMEOUT:
+    case STRIKE_REGISTER_FAILURE:
+    default:
+      LOG(DFATAL) << "Unexpected server nonce error: " << nonce_error;
+      return SERVER_NONCE_NOT_UNIQUE_FAILURE;
+  }
+}
+
+QuicCryptoServerConfig::Config::Config()
+    : channel_id_enabled(false),
+      is_primary(false),
+      primary_time(QuicWallTime::Zero()),
+      priority(0),
+      source_address_token_boxer(nullptr) {}
+
+QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); }
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_crypto_server_config.h b/net/quic/crypto/quic_crypto_server_config.h
new file mode 100644
index 0000000..ef8f6e9
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_server_config.h
@@ -0,0 +1,497 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
+#define NET_QUIC_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_secret_boxer.h"
+#include "net/quic/crypto/source_address_token.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+class EphemeralKeySource;
+class KeyExchange;
+class ProofSource;
+class QuicClock;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicRandom;
+class QuicServerConfigProtobuf;
+class StrikeRegister;
+class StrikeRegisterClient;
+
+struct ClientHelloInfo;
+
+namespace test {
+class QuicCryptoServerConfigPeer;
+}  // namespace test
+
+// Hook that allows application code to subscribe to primary config changes.
+class PrimaryConfigChangedCallback {
+ public:
+  PrimaryConfigChangedCallback();
+  virtual ~PrimaryConfigChangedCallback();
+  virtual void Run(const std::string& scid) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrimaryConfigChangedCallback);
+};
+
+// Callback used to accept the result of the |client_hello| validation step.
+class NET_EXPORT_PRIVATE ValidateClientHelloResultCallback {
+ public:
+  // Opaque token that holds information about the client_hello and
+  // its validity.  Can be interpreted by calling ProcessClientHello.
+  struct Result;
+
+  ValidateClientHelloResultCallback();
+  virtual ~ValidateClientHelloResultCallback();
+  void Run(const Result* result);
+
+ protected:
+  virtual void RunImpl(const CryptoHandshakeMessage& client_hello,
+                       const Result& result) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ValidateClientHelloResultCallback);
+};
+
+// QuicCryptoServerConfig contains the crypto configuration of a QUIC server.
+// Unlike a client, a QUIC server can have multiple configurations active in
+// order to support clients resuming with a previous configuration.
+// TODO(agl): when adding configurations at runtime is added, this object will
+// need to consider locking.
+class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
+ public:
+  // ConfigOptions contains options for generating server configs.
+  struct NET_EXPORT_PRIVATE ConfigOptions {
+    ConfigOptions();
+
+    // expiry_time is the time, in UNIX seconds, when the server config will
+    // expire. If unset, it defaults to the current time plus six months.
+    QuicWallTime expiry_time;
+    // channel_id_enabled controls whether the server config will indicate
+    // support for ChannelIDs.
+    bool channel_id_enabled;
+    // id contains the server config id for the resulting config. If empty, a
+    // random id is generated.
+    std::string id;
+    // orbit contains the kOrbitSize bytes of the orbit value for the server
+    // config. If |orbit| is empty then a random orbit is generated.
+    std::string orbit;
+    // p256 determines whether a P-256 public key will be included in the
+    // server config. Note that this breaks deterministic server-config
+    // generation since P-256 key generation doesn't use the QuicRandom given
+    // to DefaultConfig().
+    bool p256;
+  };
+
+  // |source_address_token_secret|: secret key material used for encrypting and
+  //     decrypting source address tokens. It can be of any length as it is fed
+  //     into a KDF before use. In tests, use TESTING.
+  // |server_nonce_entropy|: an entropy source used to generate the orbit and
+  //     key for server nonces, which are always local to a given instance of a
+  //     server.
+  QuicCryptoServerConfig(base::StringPiece source_address_token_secret,
+                         QuicRandom* server_nonce_entropy);
+  ~QuicCryptoServerConfig();
+
+  // TESTING is a magic parameter for passing to the constructor in tests.
+  static const char TESTING[];
+
+  // Generates a QuicServerConfigProtobuf protobuf suitable for
+  // AddConfig and SetConfigs.
+  static QuicServerConfigProtobuf* GenerateConfig(
+      QuicRandom* rand,
+      const QuicClock* clock,
+      const ConfigOptions& options);
+
+  // AddConfig adds a QuicServerConfigProtobuf to the availible configurations.
+  // It returns the SCFG message from the config if successful. The caller
+  // takes ownership of the CryptoHandshakeMessage. |now| is used in
+  // conjunction with |protobuf->primary_time()| to determine whether the
+  // config should be made primary.
+  CryptoHandshakeMessage* AddConfig(QuicServerConfigProtobuf* protobuf,
+                                    QuicWallTime now);
+
+  // AddDefaultConfig calls DefaultConfig to create a config and then calls
+  // AddConfig to add it. See the comment for |DefaultConfig| for details of
+  // the arguments.
+  CryptoHandshakeMessage* AddDefaultConfig(
+      QuicRandom* rand,
+      const QuicClock* clock,
+      const ConfigOptions& options);
+
+  // SetConfigs takes a vector of config protobufs and the current time.
+  // Configs are assumed to be uniquely identified by their server config ID.
+  // Previously unknown configs are added and possibly made the primary config
+  // depending on their |primary_time| and the value of |now|. Configs that are
+  // known, but are missing from the protobufs are deleted, unless they are
+  // currently the primary config. SetConfigs returns false if any errors were
+  // encountered and no changes to the QuicCryptoServerConfig will occur.
+  bool SetConfigs(const std::vector<QuicServerConfigProtobuf*>& protobufs,
+                  QuicWallTime now);
+
+  // Get the server config ids for all known configs.
+  void GetConfigIds(std::vector<std::string>* scids) const;
+
+  // Checks |client_hello| for gross errors and determines whether it
+  // can be shown to be fresh (i.e. not a replay).  The result of the
+  // validation step must be interpreted by calling
+  // QuicCryptoServerConfig::ProcessClientHello from the done_cb.
+  //
+  // ValidateClientHello may invoke the done_cb before unrolling the
+  // stack if it is able to assess the validity of the client_nonce
+  // without asynchronous operations.
+  //
+  // client_hello: the incoming client hello message.
+  // client_ip: the IP address of the client, which is used to generate and
+  //     validate source-address tokens.
+  // clock: used to validate client nonces and ephemeral keys.
+  // done_cb: single-use callback that accepts an opaque
+  //     ValidatedClientHelloMsg token that holds information about
+  //     the client hello.  The callback will always be called exactly
+  //     once, either under the current call stack, or after the
+  //     completion of an asynchronous operation.
+  void ValidateClientHello(
+      const CryptoHandshakeMessage& client_hello,
+      IPEndPoint client_ip,
+      const QuicClock* clock,
+      ValidateClientHelloResultCallback* done_cb) const;
+
+  // ProcessClientHello processes |client_hello| and decides whether to accept
+  // or reject the connection. If the connection is to be accepted, |out| is
+  // set to the contents of the ServerHello, |out_params| is completed and
+  // QUIC_NO_ERROR is returned. Otherwise |out| is set to be a REJ message and
+  // an error code is returned.
+  //
+  // validate_chlo_result: Output from the asynchronous call to
+  //     ValidateClientHello.  Contains the client hello message and
+  //     information about it.
+  // connection_id: the ConnectionId for the connection, which is used in key
+  //     derivation.
+  // client_address: the IP address and port of the client. The IP address is
+  //     used to generate and validate source-address tokens.
+  // version: version of the QUIC protocol in use for this connection
+  // supported_versions: versions of the QUIC protocol that this server
+  //     supports.
+  // initial_flow_control_window: size of initial flow control window this
+  //     server uses for new streams.
+  // clock: used to validate client nonces and ephemeral keys.
+  // rand: an entropy source
+  // params: the state of the handshake. This may be updated with a server
+  //     nonce when we send a rejection. After a successful handshake, this will
+  //     contain the state of the connection.
+  // out: the resulting handshake message (either REJ or SHLO)
+  // error_details: used to store a string describing any error.
+  QuicErrorCode ProcessClientHello(
+      const ValidateClientHelloResultCallback::Result& validate_chlo_result,
+      QuicConnectionId connection_id,
+      IPEndPoint client_address,
+      QuicVersion version,
+      const QuicVersionVector& supported_versions,
+      const QuicClock* clock,
+      QuicRandom* rand,
+      QuicCryptoNegotiatedParameters* params,
+      CryptoHandshakeMessage* out,
+      std::string* error_details) const;
+
+  // BuildServerConfigUpdateMessage sets |out| to be a SCUP message containing
+  // the current primary config, an up to date source-address token, and cert
+  // chain and proof in the case of secure QUIC. Returns true if successfully
+  // filled |out|.
+  //
+  // |cached_network_params| is optional, and can be nullptr.
+  bool BuildServerConfigUpdateMessage(
+      const IPEndPoint& client_ip,
+      const QuicClock* clock,
+      QuicRandom* rand,
+      const QuicCryptoNegotiatedParameters& params,
+      const CachedNetworkParameters* cached_network_params,
+      CryptoHandshakeMessage* out) const;
+
+  // SetProofSource installs |proof_source| as the ProofSource for handshakes.
+  // This object takes ownership of |proof_source|.
+  void SetProofSource(ProofSource* proof_source);
+
+  // SetEphemeralKeySource installs an object that can cache ephemeral keys for
+  // a short period of time. This object takes ownership of
+  // |ephemeral_key_source|. If not set then ephemeral keys will be generated
+  // per-connection.
+  void SetEphemeralKeySource(EphemeralKeySource* ephemeral_key_source);
+
+  // Install an externall created StrikeRegisterClient for use to
+  // interact with the strike register.  This object takes ownership
+  // of the |strike_register_client|.
+  void SetStrikeRegisterClient(StrikeRegisterClient* strike_register_client);
+
+  // set_replay_protection controls whether replay protection is enabled. If
+  // replay protection is disabled then no strike registers are needed and
+  // frontends can share an orbit value without a shared strike-register.
+  // However, an attacker can duplicate a handshake and cause a client's
+  // request to be processed twice.
+  void set_replay_protection(bool on);
+
+  // set_strike_register_no_startup_period configures the strike register to
+  // not have a startup period.
+  void set_strike_register_no_startup_period();
+
+  // set_strike_register_max_entries sets the maximum number of entries that
+  // the internal strike register will hold. If the strike register fills up
+  // then the oldest entries (by the client's clock) will be dropped.
+  void set_strike_register_max_entries(uint32 max_entries);
+
+  // set_strike_register_window_secs sets the number of seconds around the
+  // current time that the strike register will attempt to be authoritative
+  // for. Setting a larger value allows for greater client clock-skew, but
+  // means that the quiescent startup period must be longer.
+  void set_strike_register_window_secs(uint32 window_secs);
+
+  // set_source_address_token_future_secs sets the number of seconds into the
+  // future that source-address tokens will be accepted from. Since
+  // source-address tokens are authenticated, this should only happen if
+  // another, valid server has clock-skew.
+  void set_source_address_token_future_secs(uint32 future_secs);
+
+  // set_source_address_token_lifetime_secs sets the number of seconds that a
+  // source-address token will be valid for.
+  void set_source_address_token_lifetime_secs(uint32 lifetime_secs);
+
+  // set_server_nonce_strike_register_max_entries sets the number of entries in
+  // the server-nonce strike-register. This is used to record that server nonce
+  // values have been used. If the number of entries is too small then clients
+  // which are depending on server nonces may fail to handshake because their
+  // nonce has expired in the amount of time it took to go from the server to
+  // the client and back.
+  void set_server_nonce_strike_register_max_entries(uint32 max_entries);
+
+  // set_server_nonce_strike_register_window_secs sets the number of seconds
+  // around the current time that the server-nonce strike-register will accept
+  // nonces from. Setting a larger value allows for clients to delay follow-up
+  // client hellos for longer and still use server nonces as proofs of
+  // uniqueness.
+  void set_server_nonce_strike_register_window_secs(uint32 window_secs);
+
+  // Set and take ownership of the callback to invoke on primary config changes.
+  void AcquirePrimaryConfigChangedCb(PrimaryConfigChangedCallback* cb);
+
+ private:
+  friend class test::QuicCryptoServerConfigPeer;
+
+  // Config represents a server config: a collection of preferences and
+  // Diffie-Hellman public values.
+  class NET_EXPORT_PRIVATE Config : public QuicCryptoConfig,
+                                    public base::RefCounted<Config> {
+   public:
+    Config();
+
+    // TODO(rtenneti): since this is a class, we should probably do
+    // getters/setters here.
+    // |serialized| contains the bytes of this server config, suitable for
+    // sending on the wire.
+    std::string serialized;
+    // id contains the SCID of this server config.
+    std::string id;
+    // orbit contains the orbit value for this config: an opaque identifier
+    // used to identify clusters of server frontends.
+    unsigned char orbit[kOrbitSize];
+
+    // key_exchanges contains key exchange objects with the private keys
+    // already loaded. The values correspond, one-to-one, with the tags in
+    // |kexs| from the parent class.
+    std::vector<KeyExchange*> key_exchanges;
+
+    // tag_value_map contains the raw key/value pairs for the config.
+    QuicTagValueMap tag_value_map;
+
+    // channel_id_enabled is true if the config in |serialized| specifies that
+    // ChannelIDs are supported.
+    bool channel_id_enabled;
+
+    // is_primary is true if this config is the one that we'll give out to
+    // clients as the current one.
+    bool is_primary;
+
+    // primary_time contains the timestamp when this config should become the
+    // primary config. A value of QuicWallTime::Zero() means that this config
+    // will not be promoted at a specific time.
+    QuicWallTime primary_time;
+
+    // Secondary sort key for use when selecting primary configs and
+    // there are multiple configs with the same primary time.
+    // Smaller numbers mean higher priority.
+    uint64 priority;
+
+    // source_address_token_boxer_ is used to protect the
+    // source-address tokens that are given to clients.
+    // Points to either source_address_token_boxer_storage or the
+    // default boxer provided by QuicCryptoServerConfig.
+    const CryptoSecretBoxer* source_address_token_boxer;
+
+    // Holds the override source_address_token_boxer instance if the
+    // Config is not using the default source address token boxer
+    // instance provided by QuicCryptoServerConfig.
+    scoped_ptr<CryptoSecretBoxer> source_address_token_boxer_storage;
+
+   private:
+    friend class base::RefCounted<Config>;
+
+    virtual ~Config();
+
+    DISALLOW_COPY_AND_ASSIGN(Config);
+  };
+
+  typedef std::map<ServerConfigID, scoped_refptr<Config> > ConfigMap;
+
+  // Get a ref to the config with a given server config id.
+  scoped_refptr<Config> GetConfigWithScid(
+      base::StringPiece requested_scid) const;
+
+  // ConfigPrimaryTimeLessThan returns true if a->primary_time <
+  // b->primary_time.
+  static bool ConfigPrimaryTimeLessThan(const scoped_refptr<Config>& a,
+                                        const scoped_refptr<Config>& b);
+
+  // SelectNewPrimaryConfig reevaluates the primary config based on the
+  // "primary_time" deadlines contained in each.
+  void SelectNewPrimaryConfig(QuicWallTime now) const;
+
+  // EvaluateClientHello checks |client_hello| for gross errors and determines
+  // whether it can be shown to be fresh (i.e. not a replay). The results are
+  // written to |info|.
+  void EvaluateClientHello(
+      const uint8* primary_orbit,
+      scoped_refptr<Config> requested_config,
+      ValidateClientHelloResultCallback::Result* client_hello_state,
+      ValidateClientHelloResultCallback* done_cb) const;
+
+  // BuildRejection sets |out| to be a REJ message in reply to |client_hello|.
+  void BuildRejection(
+      const Config& config,
+      const CryptoHandshakeMessage& client_hello,
+      const ClientHelloInfo& info,
+      QuicRandom* rand,
+      QuicCryptoNegotiatedParameters *params,
+      CryptoHandshakeMessage* out) const;
+
+  // ParseConfigProtobuf parses the given config protobuf and returns a
+  // scoped_refptr<Config> if successful. The caller adopts the reference to the
+  // Config. On error, ParseConfigProtobuf returns nullptr.
+  scoped_refptr<Config> ParseConfigProtobuf(QuicServerConfigProtobuf* protobuf);
+
+  // NewSourceAddressToken returns a fresh source address token for the given
+  // IP address. |cached_network_params| is optional, and can be nullptr.
+  std::string NewSourceAddressToken(
+      const Config& config,
+      const IPEndPoint& ip,
+      QuicRandom* rand,
+      QuicWallTime now,
+      const CachedNetworkParameters* cached_network_params) const;
+
+  // ValidateSourceAddressToken returns HANDSHAKE_OK if the source address token
+  // in |token| is a valid and timely token for the IP address |ip| given that
+  // the current time is |now|. Otherwise it returns the reason for failure.
+  HandshakeFailureReason ValidateSourceAddressToken(const Config& config,
+                                                    base::StringPiece token,
+                                                    const IPEndPoint& ip,
+                                                    QuicWallTime now) const;
+
+  // NewServerNonce generates and encrypts a random nonce.
+  std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const;
+
+  // ValidateServerNonce decrypts |token| and verifies that it hasn't been
+  // previously used and is recent enough that it is plausible that it was part
+  // of a very recently provided rejection ("recent" will be on the order of
+  // 10-30 seconds). If so, it records that it has been used and returns
+  // HANDSHAKE_OK. Otherwise it returns the reason for failure.
+  HandshakeFailureReason ValidateServerNonce(
+      base::StringPiece echoed_server_nonce,
+      QuicWallTime now) const;
+
+  // replay_protection_ controls whether the server enforces that handshakes
+  // aren't replays.
+  bool replay_protection_;
+
+  // configs_ satisfies the following invariants:
+  //   1) configs_.empty() <-> primary_config_ == nullptr
+  //   2) primary_config_ != nullptr -> primary_config_->is_primary
+  //   3) ∀ c∈configs_, c->is_primary <-> c == primary_config_
+  mutable base::Lock configs_lock_;
+  // configs_ contains all active server configs. It's expected that there are
+  // about half-a-dozen configs active at any one time.
+  ConfigMap configs_;
+  // primary_config_ points to a Config (which is also in |configs_|) which is
+  // the primary config - i.e. the one that we'll give out to new clients.
+  mutable scoped_refptr<Config> primary_config_;
+  // next_config_promotion_time_ contains the nearest, future time when an
+  // active config will be promoted to primary.
+  mutable QuicWallTime next_config_promotion_time_;
+  // Callback to invoke when the primary config changes.
+  scoped_ptr<PrimaryConfigChangedCallback> primary_config_changed_cb_;
+
+  // Protects access to the pointer held by strike_register_client_.
+  mutable base::Lock strike_register_client_lock_;
+  // strike_register_ contains a data structure that keeps track of previously
+  // observed client nonces in order to prevent replay attacks.
+  mutable scoped_ptr<StrikeRegisterClient> strike_register_client_;
+
+  // Default source_address_token_boxer_ used to protect the
+  // source-address tokens that are given to clients.  Individual
+  // configs may use boxers with alternate secrets.
+  CryptoSecretBoxer default_source_address_token_boxer_;
+
+  // server_nonce_boxer_ is used to encrypt and validate suggested server
+  // nonces.
+  CryptoSecretBoxer server_nonce_boxer_;
+
+  // server_nonce_orbit_ contains the random, per-server orbit values that this
+  // server will use to generate server nonces (the moral equivalent of a SYN
+  // cookies).
+  uint8 server_nonce_orbit_[8];
+
+  mutable base::Lock server_nonce_strike_register_lock_;
+  // server_nonce_strike_register_ contains a data structure that keeps track of
+  // previously observed server nonces from this server, in order to prevent
+  // replay attacks.
+  mutable scoped_ptr<StrikeRegister> server_nonce_strike_register_;
+
+  // proof_source_ contains an object that can provide certificate chains and
+  // signatures.
+  scoped_ptr<ProofSource> proof_source_;
+
+  // ephemeral_key_source_ contains an object that caches ephemeral keys for a
+  // short period of time.
+  scoped_ptr<EphemeralKeySource> ephemeral_key_source_;
+
+  // These fields store configuration values. See the comments for their
+  // respective setter functions.
+  bool strike_register_no_startup_period_;
+  uint32 strike_register_max_entries_;
+  uint32 strike_register_window_secs_;
+  uint32 source_address_token_future_secs_;
+  uint32 source_address_token_lifetime_secs_;
+  uint32 server_nonce_strike_register_max_entries_;
+  uint32 server_nonce_strike_register_window_secs_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoServerConfig);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_CRYPTO_SERVER_CONFIG_H_
diff --git a/net/quic/crypto/quic_crypto_server_config_test.cc b/net/quic/crypto/quic_crypto_server_config_test.cc
new file mode 100644
index 0000000..f44dc5c
--- /dev/null
+++ b/net/quic/crypto/quic_crypto_server_config_test.cc
@@ -0,0 +1,669 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_crypto_server_config.h"
+
+#include <stdarg.h>
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_secret_boxer.h"
+#include "net/quic/crypto/crypto_server_config_protobuf.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/crypto/strike_register_client.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class QuicCryptoServerConfigPeer {
+ public:
+  explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config)
+      : server_config_(server_config) {}
+
+  scoped_refptr<QuicCryptoServerConfig::Config> GetConfig(string config_id) {
+    base::AutoLock locked(server_config_->configs_lock_);
+    if (config_id == "<primary>") {
+      return scoped_refptr<QuicCryptoServerConfig::Config>(
+          server_config_->primary_config_);
+    } else {
+      return server_config_->GetConfigWithScid(config_id);
+    }
+  }
+
+  bool ConfigHasDefaultSourceAddressTokenBoxer(string config_id) {
+    scoped_refptr<QuicCryptoServerConfig::Config> config = GetConfig(config_id);
+    return config->source_address_token_boxer ==
+        &(server_config_->default_source_address_token_boxer_);
+  }
+
+  string NewSourceAddressToken(
+      string config_id,
+      IPEndPoint ip,
+      QuicRandom* rand,
+      QuicWallTime now) {
+    return server_config_->NewSourceAddressToken(
+        *GetConfig(config_id), ip, rand, now, nullptr);
+  }
+
+  HandshakeFailureReason ValidateSourceAddressToken(string config_id,
+                                                    StringPiece srct,
+                                                    IPEndPoint ip,
+                                                    QuicWallTime now) {
+    return server_config_->ValidateSourceAddressToken(
+        *GetConfig(config_id), srct, ip, now);
+  }
+
+  string NewServerNonce(QuicRandom* rand, QuicWallTime now) const {
+    return server_config_->NewServerNonce(rand, now);
+  }
+
+  HandshakeFailureReason ValidateServerNonce(StringPiece token,
+                                             QuicWallTime now) {
+    return server_config_->ValidateServerNonce(token, now);
+  }
+
+  base::Lock* GetStrikeRegisterClientLock() {
+    return &server_config_->strike_register_client_lock_;
+  }
+
+  // CheckConfigs compares the state of the Configs in |server_config_| to the
+  // description given as arguments. The arguments are given as
+  // nullptr-terminated pairs. The first of each pair is the server config ID of
+  // a Config. The second is a boolean describing whether the config is the
+  // primary. For example:
+  //   CheckConfigs(nullptr);  // checks that no Configs are loaded.
+  //
+  //   // Checks that exactly three Configs are loaded with the given IDs and
+  //   // status.
+  //   CheckConfigs(
+  //     "id1", false,
+  //     "id2", true,
+  //     "id3", false,
+  //     nullptr);
+  void CheckConfigs(const char* server_config_id1, ...) {
+    va_list ap;
+    va_start(ap, server_config_id1);
+
+    vector<pair<ServerConfigID, bool> > expected;
+    bool first = true;
+    for (;;) {
+      const char* server_config_id;
+      if (first) {
+        server_config_id = server_config_id1;
+        first = false;
+      } else {
+        server_config_id = va_arg(ap, const char*);
+      }
+
+      if (!server_config_id) {
+        break;
+      }
+
+      // varargs will promote the value to an int so we have to read that from
+      // the stack and cast down.
+      const bool is_primary = static_cast<bool>(va_arg(ap, int));
+      expected.push_back(make_pair(server_config_id, is_primary));
+    }
+
+    va_end(ap);
+
+    base::AutoLock locked(server_config_->configs_lock_);
+
+    ASSERT_EQ(expected.size(), server_config_->configs_.size())
+        << ConfigsDebug();
+
+    for (QuicCryptoServerConfig::ConfigMap::const_iterator
+             i = server_config_->configs_.begin();
+         i != server_config_->configs_.end(); ++i) {
+      bool found = false;
+      for (vector<pair<ServerConfigID, bool> >::iterator j = expected.begin();
+           j != expected.end(); ++j) {
+        if (i->first == j->first && i->second->is_primary == j->second) {
+          found = true;
+          j->first.clear();
+          break;
+        }
+      }
+
+      ASSERT_TRUE(found) << "Failed to find match for " << i->first
+                         << " in configs:\n" << ConfigsDebug();
+    }
+  }
+
+  // ConfigsDebug returns a string that contains debugging information about
+  // the set of Configs loaded in |server_config_| and their status.
+  // ConfigsDebug() should be called after acquiring
+  // server_config_->configs_lock_.
+  string ConfigsDebug() {
+    if (server_config_->configs_.empty()) {
+      return "No Configs in QuicCryptoServerConfig";
+    }
+
+    string s;
+
+    for (QuicCryptoServerConfig::ConfigMap::const_iterator
+             i = server_config_->configs_.begin();
+         i != server_config_->configs_.end(); ++i) {
+      const scoped_refptr<QuicCryptoServerConfig::Config> config = i->second;
+      if (config->is_primary) {
+        s += "(primary) ";
+      } else {
+        s += "          ";
+      }
+      s += config->id;
+      s += "\n";
+    }
+
+    return s;
+  }
+
+  void SelectNewPrimaryConfig(int seconds) {
+    base::AutoLock locked(server_config_->configs_lock_);
+    server_config_->SelectNewPrimaryConfig(
+        QuicWallTime::FromUNIXSeconds(seconds));
+  }
+
+ private:
+  const QuicCryptoServerConfig* server_config_;
+};
+
+class TestStrikeRegisterClient : public StrikeRegisterClient {
+ public:
+  explicit TestStrikeRegisterClient(QuicCryptoServerConfig* config)
+      : config_(config),
+        is_known_orbit_called_(false) {
+  }
+
+  virtual bool IsKnownOrbit(StringPiece orbit) const OVERRIDE {
+    // Ensure that the strike register client lock is not held.
+    QuicCryptoServerConfigPeer peer(config_);
+    base::Lock* m = peer.GetStrikeRegisterClientLock();
+    // In Chromium, we will dead lock if the lock is held by the current thread.
+    // Chromium doesn't have AssertNotHeld API call.
+    // m->AssertNotHeld();
+    base::AutoLock lock(*m);
+
+    is_known_orbit_called_ = true;
+    return true;
+  }
+
+  virtual void VerifyNonceIsValidAndUnique(
+      StringPiece nonce,
+      QuicWallTime now,
+      ResultCallback* cb) OVERRIDE {
+    LOG(FATAL) << "Not implemented";
+  }
+
+  bool is_known_orbit_called() { return is_known_orbit_called_; }
+
+ private:
+  QuicCryptoServerConfig* config_;
+  mutable bool is_known_orbit_called_;
+};
+
+TEST(QuicCryptoServerConfigTest, ServerConfig) {
+  QuicRandom* rand = QuicRandom::GetInstance();
+  QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+  MockClock clock;
+
+  scoped_ptr<CryptoHandshakeMessage>(
+      server.AddDefaultConfig(rand, &clock,
+                              QuicCryptoServerConfig::ConfigOptions()));
+}
+
+TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) {
+  QuicRandom* rand = QuicRandom::GetInstance();
+  QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+  MockClock clock;
+
+  TestStrikeRegisterClient* strike_register =
+      new TestStrikeRegisterClient(&server);
+  server.SetStrikeRegisterClient(strike_register);
+
+  QuicCryptoServerConfig::ConfigOptions options;
+  scoped_ptr<CryptoHandshakeMessage>(
+      server.AddDefaultConfig(rand, &clock, options));
+  EXPECT_TRUE(strike_register->is_known_orbit_called());
+}
+
+TEST(QuicCryptoServerConfigTest, SourceAddressTokens) {
+  const string kPrimary = "<primary>";
+  const string kOverride = "Config with custom source address token key";
+
+  MockClock clock;
+  clock.AdvanceTime(QuicTime::Delta::FromSeconds(1000000));
+
+  QuicWallTime now = clock.WallNow();
+  const QuicWallTime original_time = now;
+
+  QuicRandom* rand = QuicRandom::GetInstance();
+  QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+  QuicCryptoServerConfigPeer peer(&server);
+
+  scoped_ptr<CryptoHandshakeMessage>(
+      server.AddDefaultConfig(rand, &clock,
+                              QuicCryptoServerConfig::ConfigOptions()));
+
+  // Add a config that overrides the default boxer.
+  QuicCryptoServerConfig::ConfigOptions options;
+  options.id = kOverride;
+  scoped_ptr<QuicServerConfigProtobuf> protobuf(
+      QuicCryptoServerConfig::GenerateConfig(rand, &clock, options));
+  protobuf->set_source_address_token_secret_override("a secret key");
+  // Lower priority than the default config.
+  protobuf->set_priority(1);
+  scoped_ptr<CryptoHandshakeMessage>(
+      server.AddConfig(protobuf.get(), now));
+
+  EXPECT_TRUE(peer.ConfigHasDefaultSourceAddressTokenBoxer(kPrimary));
+  EXPECT_FALSE(peer.ConfigHasDefaultSourceAddressTokenBoxer(kOverride));
+
+  IPEndPoint ip4 = IPEndPoint(Loopback4(), 1);
+  IPEndPoint ip4d = IPEndPoint(ConvertIPv4NumberToIPv6Number(ip4.address()), 1);
+  IPEndPoint ip6 = IPEndPoint(Loopback6(), 2);
+
+  // Primary config generates configs that validate successfully.
+  const string token4 = peer.NewSourceAddressToken(kPrimary, ip4, rand, now);
+  const string token4d = peer.NewSourceAddressToken(kPrimary, ip4d, rand, now);
+  const string token6 = peer.NewSourceAddressToken(kPrimary, ip6, rand, now);
+  EXPECT_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kPrimary, token4, ip4, now));
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kPrimary, token4, ip4d, now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, token4, ip6, now));
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kPrimary, token4d, ip4, now));
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kPrimary, token4d, ip4d, now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, token4d, ip6, now));
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kPrimary, token6, ip6, now));
+
+  // Override config generates configs that validate successfully.
+  const string override_token4 = peer.NewSourceAddressToken(
+      kOverride, ip4, rand, now);
+  const string override_token6 = peer.NewSourceAddressToken(
+      kOverride, ip6, rand, now);
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kOverride, override_token4, ip4, now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DIFFERENT_IP_ADDRESS_FAILURE,
+            peer.ValidateSourceAddressToken(kOverride, override_token4, ip6,
+                                            now));
+  DCHECK_EQ(HANDSHAKE_OK, peer.ValidateSourceAddressToken(
+      kOverride, override_token6, ip6, now));
+
+  // Tokens generated by the primary config do not validate
+  // successfully against the override config, and vice versa.
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+            peer.ValidateSourceAddressToken(kOverride, token4, ip4, now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+            peer.ValidateSourceAddressToken(kOverride, token6, ip6, now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, override_token4, ip4,
+                                            now));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_DECRYPTION_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, override_token6, ip6,
+                                            now));
+
+  // Validation fails after tokens expire.
+  now = original_time.Add(QuicTime::Delta::FromSeconds(86400 * 7));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_EXPIRED_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
+
+  now = original_time.Subtract(QuicTime::Delta::FromSeconds(3600 * 2));
+  DCHECK_EQ(SOURCE_ADDRESS_TOKEN_CLOCK_SKEW_FAILURE,
+            peer.ValidateSourceAddressToken(kPrimary, token4, ip4, now));
+}
+
+TEST(QuicCryptoServerConfigTest, ValidateServerNonce) {
+  QuicRandom* rand = QuicRandom::GetInstance();
+  QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+  QuicCryptoServerConfigPeer peer(&server);
+
+  StringPiece message("hello world");
+  const size_t key_size = CryptoSecretBoxer::GetKeySize();
+  scoped_ptr<uint8[]> key(new uint8[key_size]);
+  memset(key.get(), 0x11, key_size);
+
+  CryptoSecretBoxer boxer;
+  boxer.SetKey(StringPiece(reinterpret_cast<char*>(key.get()), key_size));
+  const string box = boxer.Box(rand, message);
+  MockClock clock;
+  QuicWallTime now = clock.WallNow();
+  const QuicWallTime original_time = now;
+  EXPECT_EQ(SERVER_NONCE_DECRYPTION_FAILURE,
+            peer.ValidateServerNonce(box, now));
+
+  string server_nonce = peer.NewServerNonce(rand, now);
+  EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now));
+  EXPECT_EQ(SERVER_NONCE_NOT_UNIQUE_FAILURE,
+            peer.ValidateServerNonce(server_nonce, now));
+
+  now = original_time.Add(QuicTime::Delta::FromSeconds(1000 * 7));
+  server_nonce = peer.NewServerNonce(rand, now);
+  EXPECT_EQ(HANDSHAKE_OK, peer.ValidateServerNonce(server_nonce, now));
+}
+
+class CryptoServerConfigsTest : public ::testing::Test {
+ public:
+  CryptoServerConfigsTest()
+      : rand_(QuicRandom::GetInstance()),
+        config_(QuicCryptoServerConfig::TESTING, rand_),
+        test_peer_(&config_) {}
+
+  virtual void SetUp() {
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000));
+  }
+
+  // SetConfigs constructs suitable config protobufs and calls SetConfigs on
+  // |config_|. The arguments are given as nullptr-terminated pairs. The first
+  // of each pair is the server config ID of a Config. The second is the
+  // |primary_time| of that Config, given in epoch seconds. (Although note that,
+  // in these tests, time is set to 1000 seconds since the epoch.) For example:
+  //   SetConfigs(nullptr);  // calls |config_.SetConfigs| with no protobufs.
+  //
+  //   // Calls |config_.SetConfigs| with two protobufs: one for a Config with
+  //   // a |primary_time| of 900 and priority 1, and another with
+  //   // a |primary_time| of 1000 and priority 2.
+
+  //   CheckConfigs(
+  //     "id1", 900, 1,
+  //     "id2", 1000, 2,
+  //     nullptr);
+  //
+  // If the server config id starts with "INVALID" then the generated protobuf
+  // will be invalid.
+  void SetConfigs(const char* server_config_id1, ...) {
+    const char kOrbit[] = "12345678";
+
+    va_list ap;
+    va_start(ap, server_config_id1);
+    bool has_invalid = false;
+    bool is_empty = true;
+
+    vector<QuicServerConfigProtobuf*> protobufs;
+    bool first = true;
+    for (;;) {
+      const char* server_config_id;
+      if (first) {
+        server_config_id = server_config_id1;
+        first = false;
+      } else {
+        server_config_id = va_arg(ap, const char*);
+      }
+
+      if (!server_config_id) {
+        break;
+      }
+
+      is_empty = false;
+      int primary_time = va_arg(ap, int);
+      int priority = va_arg(ap, int);
+
+      QuicCryptoServerConfig::ConfigOptions options;
+      options.id = server_config_id;
+      options.orbit = kOrbit;
+      QuicServerConfigProtobuf* protobuf(
+          QuicCryptoServerConfig::GenerateConfig(rand_, &clock_, options));
+      protobuf->set_primary_time(primary_time);
+      protobuf->set_priority(priority);
+      if (string(server_config_id).find("INVALID") == 0) {
+        protobuf->clear_key();
+        has_invalid = true;
+      }
+      protobufs.push_back(protobuf);
+    }
+
+    ASSERT_EQ(!has_invalid && !is_empty,
+              config_.SetConfigs(protobufs, clock_.WallNow()));
+    STLDeleteElements(&protobufs);
+  }
+
+ protected:
+  QuicRandom* const rand_;
+  MockClock clock_;
+  QuicCryptoServerConfig config_;
+  QuicCryptoServerConfigPeer test_peer_;
+};
+
+TEST_F(CryptoServerConfigsTest, NoConfigs) {
+  test_peer_.CheckConfigs(nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) {
+  // Make sure that "b" is primary even though "a" comes first.
+  SetConfigs("a", 1100, 1,
+             "b", 900, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, MakePrimarySecond) {
+  // Make sure that a remains primary after b is added.
+  SetConfigs("a", 900, 1,
+             "b", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, Delete) {
+  // Ensure that configs get deleted when removed.
+  SetConfigs("a", 800, 1,
+             "b", 900, 1,
+             "c", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      "c", false,
+      nullptr);
+  SetConfigs("b", 900, 1,
+             "c", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "b", true,
+      "c", false,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, DeletePrimary) {
+  // Ensure that deleting the primary config works.
+  SetConfigs("a", 800, 1,
+             "b", 900, 1,
+             "c", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      "c", false,
+      nullptr);
+  SetConfigs("a", 800, 1,
+             "c", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "c", false,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, FailIfDeletingAllConfigs) {
+  // Ensure that configs get deleted when removed.
+  SetConfigs("a", 800, 1,
+             "b", 900, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      nullptr);
+  SetConfigs(nullptr);
+  // Config change is rejected, still using old configs.
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, ChangePrimaryTime) {
+  // Check that updates to primary time get picked up.
+  SetConfigs("a", 400, 1,
+             "b", 800, 1,
+             "c", 1200, 1,
+             nullptr);
+  test_peer_.SelectNewPrimaryConfig(500);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      "c", false,
+      nullptr);
+  SetConfigs("a", 1200, 1,
+             "b", 800, 1,
+             "c", 400, 1,
+             nullptr);
+  test_peer_.SelectNewPrimaryConfig(500);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", false,
+      "c", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, AllConfigsInThePast) {
+  // Check that the most recent config is selected.
+  SetConfigs("a", 400, 1,
+             "b", 800, 1,
+             "c", 1200, 1,
+             nullptr);
+  test_peer_.SelectNewPrimaryConfig(1500);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", false,
+      "c", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, AllConfigsInTheFuture) {
+  // Check that the first config is selected.
+  SetConfigs("a", 400, 1,
+             "b", 800, 1,
+             "c", 1200, 1,
+             nullptr);
+  test_peer_.SelectNewPrimaryConfig(100);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      "c", false,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, SortByPriority) {
+  // Check that priority is used to decide on a primary config when
+  // configs have the same primary time.
+  SetConfigs("a", 900, 1,
+             "b", 900, 2,
+             "c", 900, 3,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      "c", false,
+      nullptr);
+  test_peer_.SelectNewPrimaryConfig(800);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      "c", false,
+      nullptr);
+  test_peer_.SelectNewPrimaryConfig(1000);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      "c", false,
+      nullptr);
+
+  // Change priorities and expect sort order to change.
+  SetConfigs("a", 900, 2,
+             "b", 900, 1,
+             "c", 900, 0,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", false,
+      "c", true,
+      nullptr);
+  test_peer_.SelectNewPrimaryConfig(800);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", false,
+      "c", true,
+      nullptr);
+  test_peer_.SelectNewPrimaryConfig(1000);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", false,
+      "c", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, AdvancePrimary) {
+  // Check that a new primary config is enabled at the right time.
+  SetConfigs("a", 900, 1,
+             "b", 1100, 1,
+             nullptr);
+  test_peer_.SelectNewPrimaryConfig(1000);
+  test_peer_.CheckConfigs(
+      "a", true,
+      "b", false,
+      nullptr);
+  test_peer_.SelectNewPrimaryConfig(1101);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      nullptr);
+}
+
+TEST_F(CryptoServerConfigsTest, InvalidConfigs) {
+  // Ensure that invalid configs don't change anything.
+  SetConfigs("a", 800, 1,
+             "b", 900, 1,
+             "c", 1100, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      "c", false,
+      nullptr);
+  SetConfigs("a", 800, 1,
+             "c", 1100, 1,
+             "INVALID1", 1000, 1,
+             nullptr);
+  test_peer_.CheckConfigs(
+      "a", false,
+      "b", true,
+      "c", false,
+      nullptr);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/quic_decrypter.cc b/net/quic/crypto/quic_decrypter.cc
new file mode 100644
index 0000000..b7cb089
--- /dev/null
+++ b/net/quic/crypto/quic_decrypter.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_decrypter.h"
+
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+#include "net/quic/crypto/chacha20_poly1305_decrypter.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/null_decrypter.h"
+
+namespace net {
+
+// static
+QuicDecrypter* QuicDecrypter::Create(QuicTag algorithm) {
+  switch (algorithm) {
+    case kAESG:
+      return new Aes128Gcm12Decrypter();
+    case kCC12:
+      return new ChaCha20Poly1305Decrypter();
+    case kNULL:
+      return new NullDecrypter();
+    default:
+      LOG(FATAL) << "Unsupported algorithm: " << algorithm;
+      return nullptr;
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_decrypter.h b/net/quic/crypto/quic_decrypter.h
new file mode 100644
index 0000000..06c7637
--- /dev/null
+++ b/net/quic/crypto/quic_decrypter.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicDecrypter {
+ public:
+  virtual ~QuicDecrypter() {}
+
+  static QuicDecrypter* Create(QuicTag algorithm);
+
+  // Sets the encryption key. Returns true on success, false on failure.
+  //
+  // NOTE: The key is the client_write_key or server_write_key derived from
+  // the master secret.
+  virtual bool SetKey(base::StringPiece key) = 0;
+
+  // Sets the fixed initial bytes of the nonce. Returns true on success,
+  // false on failure.
+  //
+  // NOTE: The nonce prefix is the client_write_iv or server_write_iv
+  // derived from the master secret. A 64-bit packet sequence number will
+  // be appended to form the nonce.
+  //
+  //                          <------------ 64 bits ----------->
+  //   +---------------------+----------------------------------+
+  //   |    Fixed prefix     |      Packet sequence number      |
+  //   +---------------------+----------------------------------+
+  //                          Nonce format
+  //
+  // The security of the nonce format requires that QUIC never reuse a
+  // packet sequence number, even when retransmitting a lost packet.
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) = 0;
+
+  // Decrypt authenticates |associated_data| and |ciphertext| and then decrypts
+  // |ciphertext| into |output|, using |nonce|. |nonce| must be 8 bytes longer
+  // than the nonce prefix length returned by GetNoncePrefixSize() (of the
+  // encrypter). |output| must be as long as |ciphertext| on entry and, on
+  // successful return, the true length of the plaintext will be written to
+  // |*output_length|.
+  virtual bool Decrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece ciphertext,
+                       unsigned char* output,
+                       size_t* output_length) = 0;
+
+  // Returns a newly created QuicData object containing the decrypted
+  // |ciphertext| or nullptr if there is an error. |sequence_number| is
+  // appended to the |nonce_prefix| value provided in SetNoncePrefix()
+  // to form the nonce.
+  // TODO(wtc): add a way for DecryptPacket to report decryption failure due
+  // to non-authentic inputs, as opposed to other reasons for failure.
+  virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece ciphertext) = 0;
+
+  // For use by unit tests only.
+  virtual base::StringPiece GetKey() const = 0;
+  virtual base::StringPiece GetNoncePrefix() const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
diff --git a/net/quic/crypto/quic_encrypter.cc b/net/quic/crypto/quic_encrypter.cc
new file mode 100644
index 0000000..298a15a
--- /dev/null
+++ b/net/quic/crypto/quic_encrypter.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_encrypter.h"
+
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/chacha20_poly1305_encrypter.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/null_encrypter.h"
+
+namespace net {
+
+// static
+QuicEncrypter* QuicEncrypter::Create(QuicTag algorithm) {
+  switch (algorithm) {
+    case kAESG:
+      return new Aes128Gcm12Encrypter();
+    case kCC12:
+      return new ChaCha20Poly1305Encrypter();
+    case kNULL:
+      return new NullEncrypter();
+    default:
+      LOG(FATAL) << "Unsupported algorithm: " << algorithm;
+      return nullptr;
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_encrypter.h b/net/quic/crypto/quic_encrypter.h
new file mode 100644
index 0000000..5cb40a1
--- /dev/null
+++ b/net/quic/crypto/quic_encrypter.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicEncrypter {
+ public:
+  virtual ~QuicEncrypter() {}
+
+  static QuicEncrypter* Create(QuicTag algorithm);
+
+  // Sets the encryption key. Returns true on success, false on failure.
+  //
+  // NOTE: The key is the client_write_key or server_write_key derived from
+  // the master secret.
+  virtual bool SetKey(base::StringPiece key) = 0;
+
+  // Sets the fixed initial bytes of the nonce. Returns true on success,
+  // false on failure.
+  //
+  // NOTE: The nonce prefix is the client_write_iv or server_write_iv
+  // derived from the master secret. A 64-bit packet sequence number will
+  // be appended to form the nonce.
+  //
+  //                          <------------ 64 bits ----------->
+  //   +---------------------+----------------------------------+
+  //   |    Fixed prefix     |      Packet sequence number      |
+  //   +---------------------+----------------------------------+
+  //                          Nonce format
+  //
+  // The security of the nonce format requires that QUIC never reuse a
+  // packet sequence number, even when retransmitting a lost packet.
+  virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) = 0;
+
+  // Encrypt encrypts |plaintext| and writes the ciphertext, plus a MAC over
+  // both |associated_data| and |plaintext| to |output|, using |nonce| as the
+  // nonce. |nonce| must be |8+GetNoncePrefixSize()| bytes long and |output|
+  // must point to a buffer that is at least
+  // |GetCiphertextSize(plaintext.size()| bytes long.
+  virtual bool Encrypt(base::StringPiece nonce,
+                       base::StringPiece associated_data,
+                       base::StringPiece plaintext,
+                       unsigned char* output) = 0;
+
+  // Returns a newly created QuicData object containing the encrypted
+  // |plaintext| as well as a MAC over both |plaintext| and |associated_data|,
+  // or nullptr if there is an error. |sequence_number| is appended to the
+  // |nonce_prefix| value provided in SetNoncePrefix() to form the nonce.
+  virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  base::StringPiece associated_data,
+                                  base::StringPiece plaintext) = 0;
+
+  // GetKeySize() and GetNoncePrefixSize() tell the HKDF class how many bytes
+  // of key material needs to be derived from the master secret.
+  // NOTE: the sizes returned by GetKeySize() and GetNoncePrefixSize() are
+  // also correct for the QuicDecrypter of the same algorithm. So only
+  // QuicEncrypter has these two methods.
+
+  // Returns the size in bytes of a key for the algorithm.
+  virtual size_t GetKeySize() const = 0;
+  // Returns the size in bytes of the fixed initial part of the nonce.
+  virtual size_t GetNoncePrefixSize() const = 0;
+
+  // Returns the maximum length of plaintext that can be encrypted
+  // to ciphertext no larger than |ciphertext_size|.
+  virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const = 0;
+
+  // Returns the length of the ciphertext that would be generated by encrypting
+  // to plaintext of size |plaintext_size|.
+  virtual size_t GetCiphertextSize(size_t plaintext_size) const = 0;
+
+  // For use by unit tests only.
+  virtual base::StringPiece GetKey() const = 0;
+  virtual base::StringPiece GetNoncePrefix() const = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
diff --git a/net/quic/crypto/quic_random.cc b/net/quic/crypto/quic_random.cc
new file mode 100644
index 0000000..6f46013
--- /dev/null
+++ b/net/quic/crypto/quic_random.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_random.h"
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "crypto/random.h"
+
+namespace net {
+
+namespace {
+
+class DefaultRandom : public QuicRandom {
+ public:
+  static DefaultRandom* GetInstance();
+
+  // QuicRandom implementation
+  virtual void RandBytes(void* data, size_t len) OVERRIDE;
+  virtual uint64 RandUint64() OVERRIDE;
+  virtual void Reseed(const void* additional_entropy,
+                      size_t entropy_len) OVERRIDE;
+
+ private:
+  DefaultRandom() {};
+  virtual ~DefaultRandom() {}
+
+  friend struct DefaultSingletonTraits<DefaultRandom>;
+  DISALLOW_COPY_AND_ASSIGN(DefaultRandom);
+};
+
+DefaultRandom* DefaultRandom::GetInstance() {
+  return Singleton<DefaultRandom>::get();
+}
+
+void DefaultRandom::RandBytes(void* data, size_t len) {
+  crypto::RandBytes(data, len);
+}
+
+uint64 DefaultRandom::RandUint64() {
+  uint64 value;
+  RandBytes(&value, sizeof(value));
+  return value;
+}
+
+void DefaultRandom::Reseed(const void* additional_entropy, size_t entropy_len) {
+  // No such function exists in crypto/random.h.
+}
+
+}  // namespace
+
+// static
+QuicRandom* QuicRandom::GetInstance() { return DefaultRandom::GetInstance(); }
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_random.h b/net/quic/crypto/quic_random.h
new file mode 100644
index 0000000..ac69b85
--- /dev/null
+++ b/net/quic/crypto/quic_random.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_RANDOM_H_
+#define NET_QUIC_CRYPTO_QUIC_RANDOM_H_
+
+#include <stddef.h>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// The interface for a random number generator.
+class NET_EXPORT_PRIVATE QuicRandom {
+ public:
+  virtual ~QuicRandom() {}
+
+  // Returns the default random number generator, which is cryptographically
+  // secure and thread-safe.
+  static QuicRandom* GetInstance();
+
+  // Generates |len| random bytes in the |data| buffer.
+  virtual void RandBytes(void* data, size_t len) = 0;
+
+  // Returns a random number in the range [0, kuint64max].
+  virtual uint64 RandUint64() = 0;
+
+  // Reseeds the random number generator with additional entropy input.
+  // NOTE: the constructor of a QuicRandom object is responsible for seeding
+  // itself with enough entropy input.
+  virtual void Reseed(const void* additional_entropy, size_t entropy_len) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_RANDOM_H_
diff --git a/net/quic/crypto/quic_random_test.cc b/net/quic/crypto/quic_random_test.cc
new file mode 100644
index 0000000..a762625
--- /dev/null
+++ b/net/quic/crypto/quic_random_test.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_random.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicRandomTest, RandBytes) {
+  unsigned char buf1[16];
+  unsigned char buf2[16];
+  memset(buf1, 0xaf, sizeof(buf1));
+  memset(buf2, 0xaf, sizeof(buf2));
+  ASSERT_EQ(0, memcmp(buf1, buf2, sizeof(buf1)));
+
+  QuicRandom* rng = QuicRandom::GetInstance();
+  rng->RandBytes(buf1, sizeof(buf1));
+  EXPECT_NE(0, memcmp(buf1, buf2, sizeof(buf1)));
+}
+
+TEST(QuicRandomTest, RandUint64) {
+  QuicRandom* rng = QuicRandom::GetInstance();
+  uint64 value1 = rng->RandUint64();
+  uint64 value2 = rng->RandUint64();
+  EXPECT_NE(value1, value2);
+}
+
+TEST(QuicRandomTest, Reseed) {
+  char buf[1024];
+  memset(buf, 0xaf, sizeof(buf));
+
+  QuicRandom* rng = QuicRandom::GetInstance();
+  rng->Reseed(buf, sizeof(buf));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/crypto/quic_server_info.cc b/net/quic/crypto/quic_server_info.cc
new file mode 100644
index 0000000..519d7b5
--- /dev/null
+++ b/net/quic/crypto/quic_server_info.cc
@@ -0,0 +1,141 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/quic_server_info.h"
+
+#include <limits>
+
+#include "base/pickle.h"
+
+using std::string;
+
+namespace {
+
+const int kQuicCryptoConfigVersion = 1;
+
+}  // namespace
+
+namespace net {
+
+QuicServerInfo::State::State() {}
+
+QuicServerInfo::State::~State() {}
+
+void QuicServerInfo::State::Clear() {
+  server_config.clear();
+  source_address_token.clear();
+  server_config_sig.clear();
+  certs.clear();
+}
+
+QuicServerInfo::QuicServerInfo(const QuicServerId& server_id)
+    : server_id_(server_id) {
+}
+
+QuicServerInfo::~QuicServerInfo() {
+}
+
+const QuicServerInfo::State& QuicServerInfo::state() const {
+  return state_;
+}
+
+QuicServerInfo::State* QuicServerInfo::mutable_state() {
+  return &state_;
+}
+
+bool QuicServerInfo::Parse(const string& data) {
+  State* state = mutable_state();
+
+  state->Clear();
+
+  bool r = ParseInner(data);
+  if (!r)
+    state->Clear();
+  return r;
+}
+
+bool QuicServerInfo::ParseInner(const string& data) {
+  State* state = mutable_state();
+
+  // No data was read from the disk cache.
+  if (data.empty()) {
+    return false;
+  }
+
+  Pickle p(data.data(), data.size());
+  PickleIterator iter(p);
+
+  int version = -1;
+  if (!p.ReadInt(&iter, &version)) {
+    DVLOG(1) << "Missing version";
+    return false;
+  }
+
+  if (version != kQuicCryptoConfigVersion) {
+    DVLOG(1) << "Unsupported version";
+    return false;
+  }
+
+  if (!p.ReadString(&iter, &state->server_config)) {
+    DVLOG(1) << "Malformed server_config";
+    return false;
+  }
+  if (!p.ReadString(&iter, &state->source_address_token)) {
+    DVLOG(1) << "Malformed source_address_token";
+    return false;
+  }
+  if (!p.ReadString(&iter, &state->server_config_sig)) {
+    DVLOG(1) << "Malformed server_config_sig";
+    return false;
+  }
+
+  // Read certs.
+  uint32 num_certs;
+  if (!p.ReadUInt32(&iter, &num_certs)) {
+    DVLOG(1) << "Malformed num_certs";
+    return false;
+  }
+
+  for (uint32 i = 0; i < num_certs; i++) {
+    string cert;
+    if (!p.ReadString(&iter, &cert)) {
+      DVLOG(1) << "Malformed cert";
+      return false;
+    }
+    state->certs.push_back(cert);
+  }
+
+  return true;
+}
+
+string QuicServerInfo::Serialize() {
+  string pickled_data = SerializeInner();
+  state_.Clear();
+  return pickled_data;
+}
+
+string QuicServerInfo::SerializeInner() const {
+  Pickle p(sizeof(Pickle::Header));
+
+  if (!p.WriteInt(kQuicCryptoConfigVersion) ||
+      !p.WriteString(state_.server_config) ||
+      !p.WriteString(state_.source_address_token) ||
+      !p.WriteString(state_.server_config_sig) ||
+      state_.certs.size() > std::numeric_limits<uint32>::max() ||
+      !p.WriteUInt32(state_.certs.size())) {
+    return string();
+  }
+
+  for (size_t i = 0; i < state_.certs.size(); i++) {
+    if (!p.WriteString(state_.certs[i])) {
+      return string();
+    }
+  }
+
+  return string(reinterpret_cast<const char *>(p.data()), p.size());
+}
+
+QuicServerInfoFactory::~QuicServerInfoFactory() {}
+
+}  // namespace net
diff --git a/net/quic/crypto/quic_server_info.h b/net/quic/crypto/quic_server_info.h
new file mode 100644
index 0000000..fd54b94
--- /dev/null
+++ b/net/quic/crypto/quic_server_info.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_SERVER_INFO_H_
+#define NET_QUIC_CRYPTO_QUIC_SERVER_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_server_id.h"
+
+namespace net {
+
+class X509Certificate;
+
+// QuicServerInfo is an interface for fetching information about a QUIC server.
+// This information may be stored on disk so does not include keys or other
+// sensitive information. Primarily it's intended for caching the QUIC server's
+// crypto config.
+class NET_EXPORT_PRIVATE QuicServerInfo {
+ public:
+  QuicServerInfo(const QuicServerId& server_id);
+  virtual ~QuicServerInfo();
+
+  // Start will commence the lookup. This must be called before any other
+  // methods. By opportunistically calling this early, it may be possible to
+  // overlap this object's lookup and reduce latency.
+  virtual void Start() = 0;
+
+  // WaitForDataReady returns OK if the fetch of the requested data has
+  // completed. Otherwise it returns ERR_IO_PENDING and will call |callback| on
+  // the current thread when ready.
+  //
+  // Only a single callback can be outstanding at a given time and, in the
+  // event that WaitForDataReady returns OK, it's the caller's responsibility
+  // to delete |callback|.
+  //
+  // |callback| may be NULL, in which case ERR_IO_PENDING may still be returned
+  // but, obviously, a callback will never be made.
+  virtual int WaitForDataReady(const CompletionCallback& callback) = 0;
+
+  // Returns true if data is loaded from disk cache and ready (WaitForDataReady
+  // doesn't have a pending callback).
+  virtual bool IsDataReady() = 0;
+
+  // Returns true if the object is ready to persist data, in other words, if
+  // data is loaded from disk cache and ready and there are no pending writes.
+  virtual bool IsReadyToPersist() = 0;
+
+  // Persist allows for the server information to be updated for future users.
+  // This is a fire and forget operation: the caller may drop its reference
+  // from this object and the store operation will still complete. This can
+  // only be called once WaitForDataReady has returned OK or called its
+  // callback.
+  virtual void Persist() = 0;
+
+  struct State {
+    State();
+    ~State();
+
+    void Clear();
+
+    // This class matches QuicClientCryptoConfig::CachedState.
+    std::string server_config;         // A serialized handshake message.
+    std::string source_address_token;  // An opaque proof of IP ownership.
+    std::vector<std::string> certs;    // A list of certificates in leaf-first
+                                       // order.
+    std::string server_config_sig;     // A signature of |server_config_|.
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(State);
+  };
+
+  // Once the data is ready, it can be read using the following members. These
+  // members can then be updated before calling |Persist|.
+  const State& state() const;
+  State* mutable_state();
+
+ protected:
+  // Parse parses pickled data and fills out the public member fields of this
+  // object. It returns true iff the parse was successful. The public member
+  // fields will be set to something sane in any case.
+  bool Parse(const std::string& data);
+  std::string Serialize();
+  State state_;
+
+ private:
+  // ParseInner is a helper function for Parse.
+  bool ParseInner(const std::string& data);
+
+  // SerializeInner is a helper function for Serialize.
+  std::string SerializeInner() const;
+
+  // This is the QUIC server (hostname, port, is_https, privacy_mode) tuple for
+  // which we restore the crypto_config.
+  const QuicServerId server_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicServerInfo);
+};
+
+class QuicServerInfoFactory {
+ public:
+  virtual ~QuicServerInfoFactory();
+
+  // GetForServer returns a fresh, allocated QuicServerInfo for the given
+  // |server_id| or NULL on failure.
+  virtual QuicServerInfo* GetForServer(const QuicServerId& server_id) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_QUIC_SERVER_INFO_H_
diff --git a/net/quic/crypto/scoped_evp_aead_ctx.cc b/net/quic/crypto/scoped_evp_aead_ctx.cc
new file mode 100644
index 0000000..7facf35
--- /dev/null
+++ b/net/quic/crypto/scoped_evp_aead_ctx.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/scoped_evp_aead_ctx.h"
+
+namespace net {
+
+ScopedEVPAEADCtx::ScopedEVPAEADCtx() {
+  ctx_.aead = nullptr;
+}
+
+ScopedEVPAEADCtx::~ScopedEVPAEADCtx() {
+  if (ctx_.aead != nullptr) {
+    EVP_AEAD_CTX_cleanup(&ctx_);
+  }
+}
+
+EVP_AEAD_CTX* ScopedEVPAEADCtx::get() {
+  return &ctx_;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/scoped_evp_aead_ctx.h b/net/quic/crypto/scoped_evp_aead_ctx.h
new file mode 100644
index 0000000..f0f04c1
--- /dev/null
+++ b/net/quic/crypto/scoped_evp_aead_ctx.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_SCOPED_EVP_AEAD_CTX_H_
+#define NET_QUIC_CRYPTO_SCOPED_EVP_AEAD_CTX_H_
+
+#include <openssl/evp.h>
+
+#include "base/basictypes.h"
+
+namespace net {
+
+// ScopedEVPAEADCtx manages an EVP_AEAD_CTX object and calls the needed cleanup
+// functions when it goes out of scope.
+class ScopedEVPAEADCtx {
+ public:
+  ScopedEVPAEADCtx();
+  ~ScopedEVPAEADCtx();
+
+  EVP_AEAD_CTX* get();
+
+ private:
+  EVP_AEAD_CTX ctx_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedEVPAEADCtx);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_SCOPED_EVP_AEAD_CTX_H_
diff --git a/net/quic/crypto/source_address_token.cc b/net/quic/crypto/source_address_token.cc
new file mode 100644
index 0000000..f20c343
--- /dev/null
+++ b/net/quic/crypto/source_address_token.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/source_address_token.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+
+CachedNetworkParameters::CachedNetworkParameters() {
+}
+
+CachedNetworkParameters::~CachedNetworkParameters() {
+}
+
+SourceAddressToken::SourceAddressToken() {
+}
+
+SourceAddressToken::~SourceAddressToken() {
+}
+
+string SourceAddressToken::SerializeAsString() const {
+  string out;
+  out.push_back(ip_.size());
+  out.append(ip_);
+  string time_str = base::Int64ToString(timestamp_);
+  out.push_back(time_str.size());
+  out.append(time_str);
+  // TODO(rtenneti): Implement serialization of optional CachedNetworkParameters
+  // when they are used.
+  return out;
+}
+
+bool SourceAddressToken::ParseFromArray(const char* plaintext,
+                                        size_t plaintext_length) {
+  if (plaintext_length == 0) {
+    return false;
+  }
+  size_t ip_len = plaintext[0];
+  if (plaintext_length <= 1 + ip_len) {
+    return false;
+  }
+  size_t time_len = plaintext[1 + ip_len];
+  if (plaintext_length != 1 + ip_len + 1 + time_len) {
+    return false;
+  }
+
+  string time_str(&plaintext[1 + ip_len + 1], time_len);
+  int64 timestamp;
+  if (!base::StringToInt64(time_str, &timestamp)) {
+    return false;
+  }
+
+  ip_.assign(&plaintext[1], ip_len);
+  timestamp_ = timestamp;
+
+  // TODO(rtenneti): Implement parsing of optional CachedNetworkParameters when
+  // they are used.
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/source_address_token.h b/net/quic/crypto/source_address_token.h
new file mode 100644
index 0000000..1101351
--- /dev/null
+++ b/net/quic/crypto/source_address_token.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
+#define NET_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// TODO(rtenneti): sync with server more rationally.
+// CachedNetworkParameters contains data that can be used to choose appropriate
+// connection parameters (initial RTT, initial CWND, etc.) in new connections.
+class NET_EXPORT_PRIVATE CachedNetworkParameters {
+ public:
+  // Describes the state of the connection during which the supplied network
+  // parameters were calculated.
+  enum PreviousConnectionState {
+    SLOW_START = 0,
+    CONGESTION_AVOIDANCE = 1,
+  };
+
+  CachedNetworkParameters();
+  ~CachedNetworkParameters();
+
+  std::string serving_region() const {
+    return serving_region_;
+  }
+  void set_serving_region(base::StringPiece serving_region) {
+    serving_region_ = serving_region.as_string();
+  }
+
+  int32 bandwidth_estimate_bytes_per_second() const {
+    return bandwidth_estimate_bytes_per_second_;
+  }
+  void set_bandwidth_estimate_bytes_per_second(
+      int32 bandwidth_estimate_bytes_per_second) {
+    bandwidth_estimate_bytes_per_second_ = bandwidth_estimate_bytes_per_second;
+  }
+
+  int32 max_bandwidth_estimate_bytes_per_second() const {
+    return max_bandwidth_estimate_bytes_per_second_;
+  }
+  void set_max_bandwidth_estimate_bytes_per_second(
+      int32 max_bandwidth_estimate_bytes_per_second) {
+    max_bandwidth_estimate_bytes_per_second_ =
+        max_bandwidth_estimate_bytes_per_second;
+  }
+
+  int64 max_bandwidth_timestamp_seconds() const {
+    return max_bandwidth_timestamp_seconds_;
+  }
+  void set_max_bandwidth_timestamp_seconds(
+      int64 max_bandwidth_timestamp_seconds) {
+    max_bandwidth_timestamp_seconds_ = max_bandwidth_timestamp_seconds;
+  }
+
+  int32 min_rtt_ms() const {
+    return min_rtt_ms_;
+  }
+  void set_min_rtt_ms(int32 min_rtt_ms) {
+    min_rtt_ms_ = min_rtt_ms;
+  }
+
+  int32 previous_connection_state() const {
+    return previous_connection_state_;
+  }
+  void set_previous_connection_state(int32 previous_connection_state) {
+    previous_connection_state_ = previous_connection_state;
+  }
+
+  int64 timestamp() const { return timestamp_; }
+  void set_timestamp(int64 timestamp) { timestamp_ = timestamp; }
+
+ private:
+  // serving_region_ is used to decide whether or not the bandwidth estimate and
+  // min RTT are reasonable and if they should be used.
+  // For example a group of geographically close servers may share the same
+  // serving_region_ string if they are expected to have similar network
+  // performance.
+  std::string serving_region_;
+  // The server can supply a bandwidth estimate (in bytes/s) which it may re-use
+  // on receipt of a source-address token with this field set.
+  int32 bandwidth_estimate_bytes_per_second_;
+  // The maximum bandwidth seen by the client, not necessarily the latest.
+  int32 max_bandwidth_estimate_bytes_per_second_;
+  // Timestamp (seconds since UNIX epoch) that indicates when the max bandwidth
+  // was seen by the server.
+  int64 max_bandwidth_timestamp_seconds_;
+  // The min RTT seen on a previous connection can be used by the server to
+  // inform initial connection parameters for new connections.
+  int32 min_rtt_ms_;
+  // Encodes the PreviousConnectionState enum.
+  int32 previous_connection_state_;
+  // UNIX timestamp when this bandwidth estimate was created.
+  int64 timestamp_;
+};
+
+// TODO(rtenneti): sync with server more rationally.
+// A SourceAddressToken is serialised, encrypted and sent to clients so that
+// they can prove ownership of an IP address.
+class NET_EXPORT_PRIVATE SourceAddressToken {
+ public:
+  SourceAddressToken();
+  ~SourceAddressToken();
+
+  std::string SerializeAsString() const;
+
+  bool ParseFromArray(const char* plaintext, size_t plaintext_length);
+
+  std::string ip() const {
+    return ip_;
+  }
+  void set_ip(base::StringPiece ip) {
+    ip_ = ip.as_string();
+  }
+
+  int64 timestamp() const {
+    return timestamp_;
+  }
+  void set_timestamp(int64 timestamp) {
+    timestamp_ = timestamp;
+  }
+
+  const CachedNetworkParameters& cached_network_parameters() const {
+    return cached_network_parameters_;
+  }
+  void set_cached_network_parameters(
+      const CachedNetworkParameters& cached_network_parameters) {
+    cached_network_parameters_ = cached_network_parameters;
+  }
+
+ private:
+  // ip_ contains either 4 (IPv4) or 16 (IPv6) bytes of IP address in network
+  // byte order.
+  std::string ip_;
+  // timestamp_ contains a UNIX timestamp value of the time when the token was
+  // created.
+  int64 timestamp_;
+
+  // The server can provide estimated network parameters to be used for
+  // initial parameter selection in future connections.
+  CachedNetworkParameters cached_network_parameters_;
+
+  DISALLOW_COPY_AND_ASSIGN(SourceAddressToken);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
diff --git a/net/quic/crypto/strike_register.cc b/net/quic/crypto/strike_register.cc
new file mode 100644
index 0000000..d727ece
--- /dev/null
+++ b/net/quic/crypto/strike_register.cc
@@ -0,0 +1,520 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/strike_register.h"
+
+#include <limits>
+
+#include "base/logging.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+using std::pair;
+using std::set;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+uint32 GetInitialHorizon(uint32 current_time_internal,
+                         uint32 window_secs,
+                         StrikeRegister::StartupType startup) {
+  if (startup == StrikeRegister::DENY_REQUESTS_AT_STARTUP) {
+    // The horizon is initially set |window_secs| into the future because, if
+    // we just crashed, then we may have accepted nonces in the span
+    // [current_time...current_time+window_secs] and so we conservatively
+    // reject the whole timespan unless |startup| tells us otherwise.
+    return current_time_internal + window_secs + 1;
+  } else {  // startup == StrikeRegister::NO_STARTUP_PERIOD_NEEDED
+    // The orbit can be assumed to be globally unique.  Use a horizon
+    // in the past.
+    return 0;
+  }
+}
+
+}  // namespace
+
+// static
+const uint32 StrikeRegister::kExternalNodeSize = 24;
+// static
+const uint32 StrikeRegister::kNil = (1u << 31) | 1;
+// static
+const uint32 StrikeRegister::kExternalFlag = 1 << 23;
+
+// InternalNode represents a non-leaf node in the critbit tree. See the comment
+// in the .h file for details.
+class StrikeRegister::InternalNode {
+ public:
+  void SetChild(unsigned direction, uint32 child) {
+    data_[direction] = (data_[direction] & 0xff) | (child << 8);
+  }
+
+  void SetCritByte(uint8 critbyte) {
+    data_[0] = (data_[0] & 0xffffff00) | critbyte;
+  }
+
+  void SetOtherBits(uint8 otherbits) {
+    data_[1] = (data_[1] & 0xffffff00) | otherbits;
+  }
+
+  void SetNextPtr(uint32 next) { data_[0] = next; }
+
+  uint32 next() const { return data_[0]; }
+
+  uint32 child(unsigned n) const { return data_[n] >> 8; }
+
+  uint8 critbyte() const { return data_[0]; }
+
+  uint8 otherbits() const { return data_[1]; }
+
+  // These bytes are organised thus:
+  //   <24 bits> left child
+  //   <8 bits> crit-byte
+  //   <24 bits> right child
+  //   <8 bits> other-bits
+  uint32 data_[2];
+};
+
+// kCreationTimeFromInternalEpoch contains the number of seconds between the
+// start of the internal epoch and the creation time. This allows us
+// to consider times that are before the creation time.
+static const uint32 kCreationTimeFromInternalEpoch = 63115200;  // 2 years.
+
+void StrikeRegister::ValidateStrikeRegisterConfig(unsigned max_entries) {
+  // We only have 23 bits of index available.
+  CHECK_LT(max_entries, 1u << 23);
+  CHECK_GT(max_entries, 1u);           // There must be at least two entries.
+  CHECK_EQ(sizeof(InternalNode), 8u);  // in case of compiler changes.
+}
+
+StrikeRegister::StrikeRegister(unsigned max_entries,
+                               uint32 current_time,
+                               uint32 window_secs,
+                               const uint8 orbit[8],
+                               StartupType startup)
+    : max_entries_(max_entries),
+      window_secs_(window_secs),
+      internal_epoch_(current_time > kCreationTimeFromInternalEpoch
+                          ? current_time - kCreationTimeFromInternalEpoch
+                          : 0),
+      horizon_(GetInitialHorizon(
+          ExternalTimeToInternal(current_time), window_secs, startup)) {
+  memcpy(orbit_, orbit, sizeof(orbit_));
+
+  ValidateStrikeRegisterConfig(max_entries);
+  internal_nodes_ = new InternalNode[max_entries];
+  external_nodes_.reset(new uint8[kExternalNodeSize * max_entries]);
+
+  Reset();
+}
+
+StrikeRegister::~StrikeRegister() { delete[] internal_nodes_; }
+
+void StrikeRegister::Reset() {
+  // Thread a free list through all of the internal nodes.
+  internal_node_free_head_ = 0;
+  for (unsigned i = 0; i < max_entries_ - 1; i++)
+    internal_nodes_[i].SetNextPtr(i + 1);
+  internal_nodes_[max_entries_ - 1].SetNextPtr(kNil);
+
+  // Also thread a free list through the external nodes.
+  external_node_free_head_ = 0;
+  for (unsigned i = 0; i < max_entries_ - 1; i++)
+    external_node_next_ptr(i) = i + 1;
+  external_node_next_ptr(max_entries_ - 1) = kNil;
+
+  // This is the root of the tree.
+  internal_node_head_ = kNil;
+}
+
+InsertStatus StrikeRegister::Insert(const uint8 nonce[32],
+                                    uint32 current_time_external) {
+  // Make space for the insertion if the strike register is full.
+  while (external_node_free_head_ == kNil ||
+         internal_node_free_head_ == kNil) {
+    DropOldestNode();
+  }
+
+  const uint32 current_time = ExternalTimeToInternal(current_time_external);
+
+  // Check to see if the orbit is correct.
+  if (memcmp(nonce + sizeof(current_time), orbit_, sizeof(orbit_))) {
+    return NONCE_INVALID_ORBIT_FAILURE;
+  }
+
+  const uint32 nonce_time = ExternalTimeToInternal(TimeFromBytes(nonce));
+
+  // Check that the timestamp is in the valid range.
+  pair<uint32, uint32> valid_range =
+      StrikeRegister::GetValidRange(current_time);
+  if (nonce_time < valid_range.first || nonce_time > valid_range.second) {
+    return NONCE_INVALID_TIME_FAILURE;
+  }
+
+  // We strip the orbit out of the nonce.
+  uint8 value[24];
+  memcpy(value, nonce, sizeof(nonce_time));
+  memcpy(value + sizeof(nonce_time),
+         nonce + sizeof(nonce_time) + sizeof(orbit_),
+         sizeof(value) - sizeof(nonce_time));
+
+  // Find the best match to |value| in the crit-bit tree. The best match is
+  // simply the value which /could/ match |value|, if any does, so we still
+  // need a memcmp to check.
+  uint32 best_match_index = BestMatch(value);
+  if (best_match_index == kNil) {
+    // Empty tree. Just insert the new value at the root.
+    uint32 index = GetFreeExternalNode();
+    memcpy(external_node(index), value, sizeof(value));
+    internal_node_head_ = (index | kExternalFlag) << 8;
+    DCHECK_LE(horizon_, nonce_time);
+    return NONCE_OK;
+  }
+
+  const uint8* best_match = external_node(best_match_index);
+  if (memcmp(best_match, value, sizeof(value)) == 0) {
+    // We found the value in the tree.
+    return NONCE_NOT_UNIQUE_FAILURE;
+  }
+
+  // We are going to insert a new entry into the tree, so get the nodes now.
+  uint32 internal_node_index = GetFreeInternalNode();
+  uint32 external_node_index = GetFreeExternalNode();
+
+  // If we just evicted the best match, then we have to try and match again.
+  // We know that we didn't just empty the tree because we require that
+  // max_entries_ >= 2. Also, we know that it doesn't match because, if it
+  // did, it would have been returned previously.
+  if (external_node_index == best_match_index) {
+    best_match_index = BestMatch(value);
+    best_match = external_node(best_match_index);
+  }
+
+  // Now we need to find the first bit where we differ from |best_match|.
+  unsigned differing_byte;
+  uint8 new_other_bits;
+  for (differing_byte = 0; differing_byte < sizeof(value); differing_byte++) {
+    new_other_bits = value[differing_byte] ^ best_match[differing_byte];
+    if (new_other_bits) {
+      break;
+    }
+  }
+
+  // Once we have the XOR the of first differing byte in new_other_bits we need
+  // to find the most significant differing bit. We could do this with a simple
+  // for loop, testing bits 7..0. Instead we fold the bits so that we end up
+  // with a byte where all the bits below the most significant one, are set.
+  new_other_bits |= new_other_bits >> 1;
+  new_other_bits |= new_other_bits >> 2;
+  new_other_bits |= new_other_bits >> 4;
+  // Now this bit trick results in all the bits set, except the original
+  // most-significant one.
+  new_other_bits = (new_other_bits & ~(new_other_bits >> 1)) ^ 255;
+
+  // Consider the effect of ORing against |new_other_bits|. If |value| did not
+  // have the critical bit set, the result is the same as |new_other_bits|. If
+  // it did, the result is all ones.
+
+  unsigned newdirection;
+  if ((new_other_bits | value[differing_byte]) == 0xff) {
+    newdirection = 1;
+  } else {
+    newdirection = 0;
+  }
+
+  memcpy(external_node(external_node_index), value, sizeof(value));
+  InternalNode* inode = &internal_nodes_[internal_node_index];
+
+  inode->SetChild(newdirection, external_node_index | kExternalFlag);
+  inode->SetCritByte(differing_byte);
+  inode->SetOtherBits(new_other_bits);
+
+  // |where_index| is a pointer to the uint32 which needs to be updated in
+  // order to insert the new internal node into the tree. The internal nodes
+  // store the child indexes in the top 24-bits of a 32-bit word and, to keep
+  // the code simple, we define that |internal_node_head_| is organised the
+  // same way.
+  DCHECK_EQ(internal_node_head_ & 0xff, 0u);
+  uint32* where_index = &internal_node_head_;
+  while (((*where_index >> 8) & kExternalFlag) == 0) {
+    InternalNode* node = &internal_nodes_[*where_index >> 8];
+    if (node->critbyte() > differing_byte) {
+      break;
+    }
+    if (node->critbyte() == differing_byte &&
+        node->otherbits() > new_other_bits) {
+      break;
+    }
+    if (node->critbyte() == differing_byte &&
+        node->otherbits() == new_other_bits) {
+      CHECK(false);
+    }
+
+    uint8 c = value[node->critbyte()];
+    const int direction =
+        (1 + static_cast<unsigned>(node->otherbits() | c)) >> 8;
+    where_index = &node->data_[direction];
+  }
+
+  inode->SetChild(newdirection ^ 1, *where_index >> 8);
+  *where_index = (*where_index & 0xff) | (internal_node_index << 8);
+
+  DCHECK_LE(horizon_, nonce_time);
+  return NONCE_OK;
+}
+
+const uint8* StrikeRegister::orbit() const {
+  return orbit_;
+}
+
+uint32 StrikeRegister::GetCurrentValidWindowSecs(
+    uint32 current_time_external) const {
+  uint32 current_time = ExternalTimeToInternal(current_time_external);
+  pair<uint32, uint32> valid_range = StrikeRegister::GetValidRange(
+      current_time);
+  if (valid_range.second >= valid_range.first) {
+    return valid_range.second - current_time + 1;
+  } else {
+    return 0;
+  }
+}
+
+void StrikeRegister::Validate() {
+  set<uint32> free_internal_nodes;
+  for (uint32 i = internal_node_free_head_; i != kNil;
+       i = internal_nodes_[i].next()) {
+    CHECK_LT(i, max_entries_);
+    CHECK_EQ(free_internal_nodes.count(i), 0u);
+    free_internal_nodes.insert(i);
+  }
+
+  set<uint32> free_external_nodes;
+  for (uint32 i = external_node_free_head_; i != kNil;
+       i = external_node_next_ptr(i)) {
+    CHECK_LT(i, max_entries_);
+    CHECK_EQ(free_external_nodes.count(i), 0u);
+    free_external_nodes.insert(i);
+  }
+
+  set<uint32> used_external_nodes;
+  set<uint32> used_internal_nodes;
+
+  if (internal_node_head_ != kNil &&
+      ((internal_node_head_ >> 8) & kExternalFlag) == 0) {
+    vector<pair<unsigned, bool> > bits;
+    ValidateTree(internal_node_head_ >> 8, -1, bits, free_internal_nodes,
+                 free_external_nodes, &used_internal_nodes,
+                 &used_external_nodes);
+  }
+}
+
+// static
+uint32 StrikeRegister::TimeFromBytes(const uint8 d[4]) {
+  return static_cast<uint32>(d[0]) << 24 |
+         static_cast<uint32>(d[1]) << 16 |
+         static_cast<uint32>(d[2]) << 8 |
+         static_cast<uint32>(d[3]);
+}
+
+pair<uint32, uint32> StrikeRegister::GetValidRange(
+    uint32 current_time_internal) const {
+  if (current_time_internal < horizon_) {
+    // Empty valid range.
+    return make_pair(std::numeric_limits<uint32>::max(), 0);
+  }
+
+  uint32 lower_bound;
+  if (current_time_internal >= window_secs_) {
+    lower_bound = max(horizon_, current_time_internal - window_secs_);
+  } else {
+    lower_bound = horizon_;
+  }
+
+  // Also limit the upper range based on horizon_.  This makes the
+  // strike register reject inserts that are far in the future and
+  // would consume strike register resources for a long time.  This
+  // allows the strike server to degrade optimally in cases where the
+  // insert rate exceeds |max_entries_ / (2 * window_secs_)| entries
+  // per second.
+  uint32 upper_bound =
+      current_time_internal + min(current_time_internal - horizon_,
+                                  window_secs_);
+
+  return make_pair(lower_bound, upper_bound);
+}
+
+uint32 StrikeRegister::ExternalTimeToInternal(uint32 external_time) const {
+  return external_time - internal_epoch_;
+}
+
+uint32 StrikeRegister::BestMatch(const uint8 v[24]) const {
+  if (internal_node_head_ == kNil) {
+    return kNil;
+  }
+
+  uint32 next = internal_node_head_ >> 8;
+  while ((next & kExternalFlag) == 0) {
+    InternalNode* node = &internal_nodes_[next];
+    uint8 b = v[node->critbyte()];
+    unsigned direction =
+        (1 + static_cast<unsigned>(node->otherbits() | b)) >> 8;
+    next = node->child(direction);
+  }
+
+  return next & ~kExternalFlag;
+}
+
+uint32& StrikeRegister::external_node_next_ptr(unsigned i) {
+  return *reinterpret_cast<uint32*>(&external_nodes_[i * kExternalNodeSize]);
+}
+
+uint8* StrikeRegister::external_node(unsigned i) {
+  return &external_nodes_[i * kExternalNodeSize];
+}
+
+uint32 StrikeRegister::GetFreeExternalNode() {
+  uint32 index = external_node_free_head_;
+  DCHECK(index != kNil);
+  external_node_free_head_ = external_node_next_ptr(index);
+  return index;
+}
+
+uint32 StrikeRegister::GetFreeInternalNode() {
+  uint32 index = internal_node_free_head_;
+  DCHECK(index != kNil);
+  internal_node_free_head_ = internal_nodes_[index].next();
+  return index;
+}
+
+void StrikeRegister::DropOldestNode() {
+  // DropOldestNode should never be called on an empty tree.
+  DCHECK(internal_node_head_ != kNil);
+
+  // An internal node in a crit-bit tree always has exactly two children.
+  // This means that, if we are removing an external node (which is one of
+  // those children), then we also need to remove an internal node. In order
+  // to do that we keep pointers to the parent (wherep) and grandparent
+  // (whereq) when walking down the tree.
+
+  uint32 p = internal_node_head_ >> 8, *wherep = &internal_node_head_,
+         *whereq = nullptr;
+  while ((p & kExternalFlag) == 0) {
+    whereq = wherep;
+    InternalNode* inode = &internal_nodes_[p];
+    // We always go left, towards the smallest element, exploiting the fact
+    // that the timestamp is big-endian and at the start of the value.
+    wherep = &inode->data_[0];
+    p = (*wherep) >> 8;
+  }
+
+  const uint32 ext_index = p & ~kExternalFlag;
+  const uint8* ext_node = external_node(ext_index);
+  uint32 new_horizon = ExternalTimeToInternal(TimeFromBytes(ext_node)) + 1;
+  DCHECK_LE(horizon_, new_horizon);
+  horizon_ = new_horizon;
+
+  if (!whereq) {
+    // We are removing the last element in a tree.
+    internal_node_head_ = kNil;
+    FreeExternalNode(ext_index);
+    return;
+  }
+
+  // |wherep| points to the left child pointer in the parent so we can add
+  // one and dereference to get the right child.
+  const uint32 other_child = wherep[1];
+  FreeInternalNode((*whereq) >> 8);
+  *whereq = (*whereq & 0xff) | (other_child & 0xffffff00);
+  FreeExternalNode(ext_index);
+}
+
+void StrikeRegister::FreeExternalNode(uint32 index) {
+  external_node_next_ptr(index) = external_node_free_head_;
+  external_node_free_head_ = index;
+}
+
+void StrikeRegister::FreeInternalNode(uint32 index) {
+  internal_nodes_[index].SetNextPtr(internal_node_free_head_);
+  internal_node_free_head_ = index;
+}
+
+void StrikeRegister::ValidateTree(
+    uint32 internal_node,
+    int last_bit,
+    const vector<pair<unsigned, bool> >& bits,
+    const set<uint32>& free_internal_nodes,
+    const set<uint32>& free_external_nodes,
+    set<uint32>* used_internal_nodes,
+    set<uint32>* used_external_nodes) {
+  CHECK_LT(internal_node, max_entries_);
+  const InternalNode* i = &internal_nodes_[internal_node];
+  unsigned bit = 0;
+  switch (i->otherbits()) {
+    case 0xff & ~(1 << 7):
+      bit = 0;
+      break;
+    case 0xff & ~(1 << 6):
+      bit = 1;
+      break;
+    case 0xff & ~(1 << 5):
+      bit = 2;
+      break;
+    case 0xff & ~(1 << 4):
+      bit = 3;
+      break;
+    case 0xff & ~(1 << 3):
+      bit = 4;
+      break;
+    case 0xff & ~(1 << 2):
+      bit = 5;
+      break;
+    case 0xff & ~(1 << 1):
+      bit = 6;
+      break;
+    case 0xff & ~1:
+      bit = 7;
+      break;
+    default:
+      CHECK(false);
+  }
+
+  bit += 8 * i->critbyte();
+  if (last_bit > -1) {
+    CHECK_GT(bit, static_cast<unsigned>(last_bit));
+  }
+
+  CHECK_EQ(free_internal_nodes.count(internal_node), 0u);
+
+  for (unsigned child = 0; child < 2; child++) {
+    if (i->child(child) & kExternalFlag) {
+      uint32 ext = i->child(child) & ~kExternalFlag;
+      CHECK_EQ(free_external_nodes.count(ext), 0u);
+      CHECK_EQ(used_external_nodes->count(ext), 0u);
+      used_external_nodes->insert(ext);
+      const uint8* bytes = external_node(ext);
+      for (vector<pair<unsigned, bool> >::const_iterator i = bits.begin();
+           i != bits.end(); i++) {
+        unsigned byte = i->first / 8;
+        DCHECK_LE(byte, 0xffu);
+        unsigned bit = i->first % 8;
+        static const uint8 kMasks[8] =
+            {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
+        CHECK_EQ((bytes[byte] & kMasks[bit]) != 0, i->second);
+      }
+    } else {
+      uint32 inter = i->child(child);
+      vector<pair<unsigned, bool> > new_bits(bits);
+      new_bits.push_back(pair<unsigned, bool>(bit, child != 0));
+      CHECK_EQ(free_internal_nodes.count(inter), 0u);
+      CHECK_EQ(used_internal_nodes->count(inter), 0u);
+      used_internal_nodes->insert(inter);
+      ValidateTree(inter, bit, bits, free_internal_nodes, free_external_nodes,
+                   used_internal_nodes, used_external_nodes);
+    }
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/crypto/strike_register.h b/net/quic/crypto/strike_register.h
new file mode 100644
index 0000000..8acc272
--- /dev/null
+++ b/net/quic/crypto/strike_register.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
+#define NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// InsertStatus enum values cannot be changed, they need to be stable.
+enum InsertStatus {
+  NONCE_OK = 0,
+  // The default error value for nonce verification failures from strike
+  // register (covers old strike registers and unknown failures).
+  NONCE_UNKNOWN_FAILURE = 1,
+  // Decrypted nonce had incorrect length.
+  NONCE_INVALID_FAILURE = 2,
+  // Nonce is not unique.
+  NONCE_NOT_UNIQUE_FAILURE = 3,
+  // Nonce's orbit is invalid or incorrect.
+  NONCE_INVALID_ORBIT_FAILURE = 4,
+  // Nonce's timestamp is not in the strike register's valid time range.
+  NONCE_INVALID_TIME_FAILURE = 5,
+  // Strike register's RPC call timed out, nonce couldn't be verified.
+  STRIKE_REGISTER_TIMEOUT = 6,
+  // Strike register is down, nonce couldn't be verified.
+  STRIKE_REGISTER_FAILURE = 7,
+};
+
+// A StrikeRegister is critbit tree which stores a set of observed nonces.
+// We use a critbit tree because:
+//   1) It's immune to algorithmic complexity attacks. If we had used a hash
+//      tree, an attacker could send us a series of values which stretch out one
+//      of the hash chains, causing us to do much more work than normal.
+//   2) We can write it to use a fixed block of memory: avoiding fragmentation
+//      issues and so forth. (We might be able to do that with the STL
+//      algorithms and a custom allocator, but I don't want to go there.)
+//   3) It's simple (compared to balanced binary trees) and doesn't involve
+//      bouncing nearly as many cache lines around.
+//   4) It allows us to query for the oldest element in log(n) time.
+//
+// This code is based on djb's public domain critbit tree from qhasm.
+//
+// A critbit tree has external and internal nodes. External nodes are just the
+// nonce values (which are stored with internal times, see below, and without
+// the orbit values included). Internal nodes contain the bit number at which
+// the tree is branching and exactly two children. The critical bit is stored
+// as a byte number and a byte (|otherbits|) which has all the bits set
+// /except/ the one in question.
+//
+// Internal nodes have exactly two children: an internal node with only a
+// single child would be useless.
+//
+// The branching bit number (considering the MSB to be the 1st bit) is
+// monotonically increasing as you go down the tree.
+//
+// There are two distinct time representations used. External times are those
+// which are exposed to the users of this class. They are expected to be a
+// count of the number of seconds since the UNIX epoch. Internal times are a
+// count of the number of seconds since a point in time a couple of years
+// before the creation time given to the constructor. (See
+// |ExternalTimeToInternal|) This avoids having to worry about overflow since
+// we assume that no process will run for 130 years.
+class NET_EXPORT_PRIVATE StrikeRegister {
+ public:
+  enum StartupType {
+    // DENY_REQUESTS_AT_STARTUP is the typical mode for a strike register.
+    // Because servers can crash and the strike-register memory-based, the
+    // state of the strike-register may be lost at any time. Thus the previous
+    // instance of the server may have accepted an nonce with time
+    // now+window_secs, which was forgotten in the crash. Therefore
+    // DENY_REQUESTS_AT_STARTUP causes the strike-register to reject all
+    // requests timestampped before window_secs + the creation time (the
+    // quiescent period).
+    DENY_REQUESTS_AT_STARTUP,
+    // NO_STARTUP_PERIOD_NEEDED indicates that no quiescent period is required.
+    // This may be because the strike-register is using an orbit randomly
+    // generated at startup and therefore nonces accepted by the previous
+    // instance of the strike-register are invalid for that reason.
+    NO_STARTUP_PERIOD_NEEDED,
+  };
+
+  // An external node takes 24 bytes as we don't record the orbit.
+  static const uint32 kExternalNodeSize;
+
+  // We address the nodes by their index in the array. This means that 0 is a
+  // valid index. Therefore this is our invalid index. It also has a one bit
+  // in the LSB position because we tend to store indexes shifted up 8 bits
+  // and this distinguishes kNil from (kExternalFlag | 0) << 8.
+  static const uint32 kNil;
+
+  // Our pointers from internal nodes can either point to an internal or
+  // external node. We flag the 24th bit to mark a pointer as external.
+  static const uint32 kExternalFlag;
+
+  // Allows early validation before a strike register is created.
+  static void ValidateStrikeRegisterConfig(unsigned max_entries);
+
+  // Construct a new set which can hold, at most, |max_entries| (which must be
+  // less than 2**23). See the comments around StartupType about initial
+  // behaviour. Otherwise, all nonces that are outside +/- |window_secs| from
+  // the current time will be rejected. Additionally, all nonces that have an
+  // orbit value other than |orbit| will be rejected.
+  //
+  // (Note that this code is independent of the actual units of time used, but
+  // you should use seconds.)
+  StrikeRegister(unsigned max_entries,
+                 uint32 current_time_external,
+                 uint32 window_secs,
+                 const uint8 orbit[8],
+                 StartupType startup);
+
+  ~StrikeRegister();
+
+  void Reset();
+
+  // |Insert| queries to see if |nonce| is
+  //   a) for the wrong orbit
+  //   b) before the current horizon
+  //   c) outside of the valid time window
+  //   d) already in the set of observed nonces
+  // and returns the failure reason if any of these are true. It is also free to
+  // return failure reason for other reasons as it's always safe to reject an
+  // nonce.
+  //
+  // nonces are:
+  //   4 bytes of timestamp (UNIX epoch seconds)
+  //   8 bytes of orbit value (a cluster id)
+  //   20 bytes of random data
+  //
+  // Otherwise, it inserts |nonce| into the observed set and returns NONCE_OK.
+  InsertStatus Insert(const uint8 nonce[32], uint32 current_time);
+
+  // orbit returns a pointer to the 8-byte orbit value for this
+  // strike-register.
+  const uint8* orbit() const;
+
+  // Time window for which the strike register has complete information.
+  uint32 GetCurrentValidWindowSecs(uint32 current_time_external) const;
+
+  // This is a debugging aid which checks the tree for sanity.
+  void Validate();
+
+ private:
+  class InternalNode;
+
+  // TimeFromBytes returns a big-endian uint32 from |d|.
+  static uint32 TimeFromBytes(const uint8 d[4]);
+
+  // Range of internal times for which the strike register has
+  // complete information.  A nonce is within the valid range of the
+  // strike register if:
+  //   valid_range.first <= nonce_time_internal <= valid_range.second
+  std::pair<uint32, uint32> GetValidRange(uint32 current_time_internal) const;
+
+  // ExternalTimeToInternal converts an external time value into an internal
+  // time value using |internal_epoch_|.
+  uint32 ExternalTimeToInternal(uint32 external_time) const;
+
+  // BestMatch returns either kNil, or an external node index which could
+  // possibly match |v|.
+  uint32 BestMatch(const uint8 v[24]) const;
+
+  // external_node_next_ptr returns the 'next' pointer embedded in external
+  // node |i|. This is used to thread a free list through the external nodes.
+  uint32& external_node_next_ptr(unsigned i);
+
+  uint8* external_node(unsigned i);
+
+  uint32 GetFreeExternalNode();
+
+  uint32 GetFreeInternalNode();
+
+  // DropOldestNode removes the oldest node in the tree and updates |horizon_|
+  // accordingly.
+  void DropOldestNode();
+
+  void FreeExternalNode(uint32 index);
+
+  void FreeInternalNode(uint32 index);
+
+  void ValidateTree(uint32 internal_node,
+                    int last_bit,
+                    const std::vector<std::pair<unsigned, bool> >& bits,
+                    const std::set<uint32>& free_internal_nodes,
+                    const std::set<uint32>& free_external_nodes,
+                    std::set<uint32>* used_internal_nodes,
+                    std::set<uint32>* used_external_nodes);
+
+  const uint32 max_entries_;
+  const uint32 window_secs_;
+  // internal_epoch_ contains the external time value of the start of internal
+  // time.
+  const uint32 internal_epoch_;
+  uint8 orbit_[8];
+  // The strike register will reject nonces with internal times < |horizon_| .
+  uint32 horizon_;
+
+  uint32 internal_node_free_head_;
+  uint32 external_node_free_head_;
+  uint32 internal_node_head_;
+  // internal_nodes_ can't be a scoped_ptr because the type isn't defined in
+  // this header.
+  InternalNode* internal_nodes_;
+  scoped_ptr<uint8[]> external_nodes_;
+
+  DISALLOW_COPY_AND_ASSIGN(StrikeRegister);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
diff --git a/net/quic/crypto/strike_register_client.h b/net/quic/crypto/strike_register_client.h
new file mode 100644
index 0000000..dae3519
--- /dev/null
+++ b/net/quic/crypto/strike_register_client.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_CRYPTO_STRIKE_REGISTER_CLIENT_H_
+#define NET_QUIC_CRYPTO_STRIKE_REGISTER_CLIENT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/strike_register.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// Interface implemented by clients that talk to strike registers
+// implemented as local or remote services.
+class NET_EXPORT_PRIVATE StrikeRegisterClient {
+ public:
+  // Single use callback that will be invoked once the validation
+  // operation is complete.
+  class NET_EXPORT_PRIVATE ResultCallback {
+   public:
+    ResultCallback() {}
+    virtual ~ResultCallback() {}
+    void Run(bool nonce_is_valid_and_unique, InsertStatus nonce_error) {
+      RunImpl(nonce_is_valid_and_unique, nonce_error);
+      delete this;
+    }
+
+   protected:
+    virtual void RunImpl(bool nonce_is_valid_and_unique,
+                         InsertStatus nonce_error) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(ResultCallback);
+  };
+
+  StrikeRegisterClient() {}
+  virtual ~StrikeRegisterClient() {}
+
+  // Returns true iff the strike register knows about the given orbit.
+  virtual bool IsKnownOrbit(base::StringPiece orbit) const = 0;
+  // Validate a nonce for freshness and uniqueness.
+  // Will invoke cb->Run(ValidateResponse::nonce_is_valid_and_unique(),
+  //                     ValidateResponse::nonce_error())
+  // once the asynchronous operation is complete.
+  virtual void VerifyNonceIsValidAndUnique(
+      base::StringPiece nonce,
+      QuicWallTime now,
+      ResultCallback* cb) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StrikeRegisterClient);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_CRYPTO_STRIKE_REGISTER_CLIENT_H_
diff --git a/net/quic/crypto/strike_register_test.cc b/net/quic/crypto/strike_register_test.cc
new file mode 100644
index 0000000..df72357
--- /dev/null
+++ b/net/quic/crypto/strike_register_test.cc
@@ -0,0 +1,408 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/crypto/strike_register.h"
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/rand_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using net::InsertStatus;
+using net::StrikeRegister;
+using std::make_pair;
+using std::min;
+using std::pair;
+using std::set;
+using std::string;
+
+const uint8 kOrbit[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+
+// StrikeRegisterTests don't look at the random bytes so this function can
+// simply set the random bytes to 0.
+void SetNonce(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
+  nonce[0] = time >> 24;
+  nonce[1] = time >> 16;
+  nonce[2] = time >> 8;
+  nonce[3] = time;
+  memcpy(nonce + 4, orbit, 8);
+  memset(nonce + 12, 0, 20);
+}
+
+TEST(StrikeRegisterTest, SimpleHorizon) {
+  // The set must reject values created on or before its own creation time.
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+  uint8 nonce[32];
+  SetNonce(nonce, 999, kOrbit);
+  EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
+  SetNonce(nonce, 1000, kOrbit);
+  EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
+
+  EXPECT_EQ(0u, set.GetCurrentValidWindowSecs(1000 /* current time */));
+  EXPECT_EQ(0u, set.GetCurrentValidWindowSecs(1100 /* current time */));
+  EXPECT_EQ(1u, set.GetCurrentValidWindowSecs(1101 /* current time */));
+  EXPECT_EQ(50u, set.GetCurrentValidWindowSecs(1150 /* current time */));
+  EXPECT_EQ(100u, set.GetCurrentValidWindowSecs(1200 /* current time */));
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1300 /* current time */));
+}
+
+TEST(StrikeRegisterTest, NoStartupMode) {
+  // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
+  // is specified.
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
+  uint8 nonce[32];
+  SetNonce(nonce, 1000, kOrbit);
+  EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1000));
+  EXPECT_EQ(net::NONCE_NOT_UNIQUE_FAILURE, set.Insert(nonce, 1000));
+
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1000 /* current time */));
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1050 /* current time */));
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1100 /* current time */));
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1200 /* current time */));
+  EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1300 /* current time */));
+}
+
+TEST(StrikeRegisterTest, WindowFuture) {
+  // The set must reject values outside the window.
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+  uint8 nonce[32];
+  SetNonce(nonce, 1101, kOrbit);
+  EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1000));
+  SetNonce(nonce, 999, kOrbit);
+  EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, BadOrbit) {
+  // The set must reject values with the wrong orbit
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+  uint8 nonce[32];
+  static const uint8 kBadOrbit[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+  SetNonce(nonce, 1101, kBadOrbit);
+  EXPECT_EQ(net::NONCE_INVALID_ORBIT_FAILURE, set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, OneValue) {
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+  uint8 nonce[32];
+  SetNonce(nonce, 1101, kOrbit);
+  EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1101));
+}
+
+TEST(StrikeRegisterTest, RejectDuplicate) {
+  // The set must reject values with the wrong orbit
+  StrikeRegister set(10 /* max size */, 1000 /* current time */,
+                     100 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+  uint8 nonce[32];
+  SetNonce(nonce, 1101, kOrbit);
+  EXPECT_EQ(net::NONCE_OK, set.Insert(nonce, 1101));
+  EXPECT_EQ(net::NONCE_NOT_UNIQUE_FAILURE, set.Insert(nonce, 1101));
+}
+
+TEST(StrikeRegisterTest, HorizonUpdating) {
+  StrikeRegister::StartupType startup_types[] = {
+    StrikeRegister::DENY_REQUESTS_AT_STARTUP,
+    StrikeRegister::NO_STARTUP_PERIOD_NEEDED
+  };
+
+  for (size_t type_idx = 0; type_idx < arraysize(startup_types); ++type_idx) {
+    StrikeRegister set(5 /* max size */, 500 /* current time */,
+                       100 /* window secs */, kOrbit,
+                       startup_types[type_idx]);
+    uint8 nonce[6][32];
+    for (unsigned i = 0; i < 5; i++) {
+      SetNonce(nonce[i], 1101 + i, kOrbit);
+      nonce[i][31] = i;
+      EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[i], 1100));
+    }
+
+    // Valid window is still equal to |window_secs + 1|.
+    EXPECT_EQ(101u, set.GetCurrentValidWindowSecs(1100));
+
+    // This should push the oldest value out and force the horizon to
+    // be updated.
+    SetNonce(nonce[5], 1110, kOrbit);
+    EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
+    // Effective horizon is computed based on the timestamp of the
+    // value that was pushed out.
+    EXPECT_EQ(9u, set.GetCurrentValidWindowSecs(1110));
+
+    SetNonce(nonce[5], 1111, kOrbit);
+    EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
+    EXPECT_EQ(8u, set.GetCurrentValidWindowSecs(1110));
+
+    // This should be behind the horizon now:
+    SetNonce(nonce[5], 1101, kOrbit);
+    nonce[5][31] = 10;
+    EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
+
+    // Insert beyond the valid range.
+    SetNonce(nonce[5], 1117, kOrbit);
+    nonce[5][31] = 2;
+    EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
+
+    // Insert at the upper valid range.
+    SetNonce(nonce[5], 1116, kOrbit);
+    nonce[5][31] = 1;
+    EXPECT_EQ(net::NONCE_OK, set.Insert(nonce[5], 1110));
+
+    // This should be beyond the upper valid range now:
+    SetNonce(nonce[5], 1116, kOrbit);
+    nonce[5][31] = 2;
+    EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce[5], 1110));
+  }
+}
+
+TEST(StrikeRegisterTest, InsertMany) {
+  StrikeRegister set(5000 /* max size */, 1000 /* current time */,
+                     500 /* window secs */, kOrbit,
+                     StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+
+  uint8 nonce[32];
+  SetNonce(nonce, 1101, kOrbit);
+  for (unsigned i = 0; i < 100000; i++) {
+    SetNonce(nonce, 1101 + i/500, kOrbit);
+    memcpy(nonce + 12, &i, sizeof(i));
+    EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, set.Insert(nonce, 1100));
+  }
+}
+
+
+// For the following test we create a slow, but simple, version of a
+// StrikeRegister. The behaviour of this object is much easier to understand
+// than the fully fledged version. We then create a test to show, empirically,
+// that the two objects have identical behaviour.
+
+// A SlowStrikeRegister has the same public interface as a StrikeRegister, but
+// is much slower. Hopefully it is also more obviously correct and we can
+// empirically test that their behaviours are identical.
+class SlowStrikeRegister {
+ public:
+  SlowStrikeRegister(unsigned max_entries, uint32 current_time,
+                     uint32 window_secs, const uint8 orbit[8])
+      : max_entries_(max_entries),
+        window_secs_(window_secs),
+        creation_time_(current_time),
+        horizon_(ExternalTimeToInternal(current_time + window_secs) + 1) {
+    memcpy(orbit_, orbit, sizeof(orbit_));
+  }
+
+  InsertStatus Insert(const uint8 nonce_bytes[32],
+                      const uint32 nonce_time_external,
+                      const uint32 current_time_external) {
+    if (nonces_.size() == max_entries_) {
+      DropOldestEntry();
+    }
+
+    const uint32 current_time = ExternalTimeToInternal(current_time_external);
+
+    // Check to see if the orbit is correct.
+    if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
+      return net::NONCE_INVALID_ORBIT_FAILURE;
+    }
+    const uint32 nonce_time =
+        ExternalTimeToInternal(TimeFromBytes(nonce_bytes));
+    EXPECT_EQ(ExternalTimeToInternal(nonce_time_external), nonce_time);
+    // We have dropped one or more nonces with a time value of |horizon_ - 1|,
+    // so we have to reject anything with a timestamp less than or
+    // equal to that.
+    if (nonce_time < horizon_) {
+      return net::NONCE_INVALID_TIME_FAILURE;
+    }
+
+    // Check that the timestamp is in the current window.
+    if ((current_time > window_secs_ &&
+         nonce_time < (current_time - window_secs_)) ||
+        nonce_time > (current_time + window_secs_)) {
+      return net::NONCE_INVALID_TIME_FAILURE;
+    }
+
+    pair<uint32, string> nonce = make_pair(
+        nonce_time,
+        string(reinterpret_cast<const char*>(nonce_bytes), 32));
+
+    set<pair<uint32, string> >::const_iterator it = nonces_.find(nonce);
+    if (it != nonces_.end()) {
+      return net::NONCE_NOT_UNIQUE_FAILURE;
+    }
+
+    nonces_.insert(nonce);
+    return net::NONCE_OK;
+  }
+
+  uint32 GetCurrentValidWindowSecs(const uint32 current_time_external) const {
+    const uint32 current_time = ExternalTimeToInternal(current_time_external);
+    if (horizon_ > current_time) {
+      return 0;
+    }
+    return 1 + min(current_time - horizon_, window_secs_);
+  }
+
+ private:
+  // TimeFromBytes returns a big-endian uint32 from |d|.
+  static uint32 TimeFromBytes(const uint8 d[4]) {
+    return static_cast<uint32>(d[0]) << 24 |
+           static_cast<uint32>(d[1]) << 16 |
+           static_cast<uint32>(d[2]) << 8 |
+           static_cast<uint32>(d[3]);
+  }
+
+  uint32 ExternalTimeToInternal(uint32 external_time) const {
+    static const uint32 kCreationTimeFromInternalEpoch = 63115200.0;
+    uint32 internal_epoch = 0;
+    if (creation_time_ > kCreationTimeFromInternalEpoch) {
+      internal_epoch = creation_time_ - kCreationTimeFromInternalEpoch;
+    }
+
+    return external_time - internal_epoch;
+  }
+
+  void DropOldestEntry() {
+    set<pair<uint32, string> >::iterator oldest = nonces_.begin();
+    horizon_ = oldest->first + 1;
+    nonces_.erase(oldest);
+  }
+
+  const unsigned max_entries_;
+  const unsigned window_secs_;
+  const uint32 creation_time_;
+  uint8 orbit_[8];
+  uint32 horizon_;
+
+  set<pair<uint32, string> > nonces_;
+};
+
+class StrikeRegisterStressTest : public ::testing::Test {
+};
+
+TEST_F(StrikeRegisterStressTest, InOrderInsertion) {
+  // Fixed seed gives reproducibility for this test.
+  srand(42);
+
+  unsigned max_entries = 64;
+  uint32 current_time = 10000, window = 200;
+  scoped_ptr<StrikeRegister> s1(
+      new StrikeRegister(max_entries, current_time, window, kOrbit,
+                         StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+  scoped_ptr<SlowStrikeRegister> s2(
+      new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
+
+  uint64 i;
+  const uint64 kMaxIterations = 10000;
+  for (i = 0; i < kMaxIterations; i++) {
+    const uint32 time = current_time + i;
+
+    uint8 nonce[32];
+    SetNonce(nonce, time, kOrbit);
+
+    // There are 2048 possible nonce values:
+    const uint32 v = rand() % 2048;
+    nonce[30] = v >> 8;
+    nonce[31] = v;
+
+    const InsertStatus nonce_error2 = s2->Insert(nonce, time, time);
+    const InsertStatus nonce_error1 = s1->Insert(nonce, time);
+    EXPECT_EQ(nonce_error1, nonce_error2);
+
+    // Inserts succeed after the startup period.
+    if (time > current_time + window) {
+      EXPECT_EQ(net::NONCE_OK, nonce_error1);
+    } else {
+      EXPECT_EQ(net::NONCE_INVALID_TIME_FAILURE, nonce_error1);
+    }
+    EXPECT_EQ(s1->GetCurrentValidWindowSecs(time),
+              s2->GetCurrentValidWindowSecs(time));
+
+    if (i % 10 == 0) {
+      s1->Validate();
+    }
+
+    if (HasFailure()) {
+      break;
+    }
+  }
+
+  if (i != kMaxIterations) {
+    FAIL() << "Failed after " << i << " iterations";
+  }
+}
+
+TEST_F(StrikeRegisterStressTest, Stress) {
+  // Fixed seed gives reproducibility for this test.
+  srand(42);
+  unsigned max_entries = 64;
+  uint32 current_time = 10000, window = 200;
+  scoped_ptr<StrikeRegister> s1(
+      new StrikeRegister(max_entries, current_time, window, kOrbit,
+                         StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+  scoped_ptr<SlowStrikeRegister> s2(
+      new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
+  uint64 i;
+
+  // When making changes it's worth removing the limit on this test and running
+  // it for a while. For the initial development an opt binary was left running
+  // for 10 minutes.
+  const uint64 kMaxIterations = 10000;
+  for (i = 0; i < kMaxIterations; i++) {
+    if (rand() % 1000 == 0) {
+      // 0.1% chance of resetting the sets.
+      max_entries = rand() % 300 + 2;
+      current_time = rand() % 10000;
+      window = rand() % 500;
+      s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit,
+                                  StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+      s2.reset(
+          new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
+    }
+
+    int32 time_delta = rand() % (window * 4);
+    time_delta -= window * 2;
+    const uint32 time = current_time + time_delta;
+    if (time_delta < 0 && time > current_time) {
+      continue;  // overflow
+    }
+
+    uint8 nonce[32];
+    SetNonce(nonce, time, kOrbit);
+
+    // There are 2048 possible nonce values:
+    const uint32 v = rand() % 2048;
+    nonce[30] = v >> 8;
+    nonce[31] = v;
+
+    const InsertStatus nonce_error2 = s2->Insert(nonce, time, time);
+    const InsertStatus nonce_error1 = s1->Insert(nonce, time);
+    EXPECT_EQ(nonce_error1, nonce_error2);
+    EXPECT_EQ(s1->GetCurrentValidWindowSecs(time),
+              s2->GetCurrentValidWindowSecs(time));
+
+    if (i % 10 == 0) {
+      s1->Validate();
+    }
+
+    if (HasFailure()) {
+      break;
+    }
+  }
+
+  if (i != kMaxIterations) {
+    FAIL() << "Failed after " << i << " iterations";
+  }
+}
+
+}  // anonymous namespace
diff --git a/net/quic/iovector.cc b/net/quic/iovector.cc
new file mode 100644
index 0000000..a6d2c61
--- /dev/null
+++ b/net/quic/iovector.cc
@@ -0,0 +1,13 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/iovector.h"
+
+namespace net {
+
+IOVector::IOVector() {}
+
+IOVector::~IOVector() {}
+
+}  // namespace net
diff --git a/net/quic/iovector.h b/net/quic/iovector.h
new file mode 100644
index 0000000..22d2cc9
--- /dev/null
+++ b/net/quic/iovector.h
@@ -0,0 +1,200 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_IOVECTOR_H_
+#define NET_QUIC_IOVECTOR_H_
+
+#include <stddef.h>
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/base/iovec.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Calculate the total number of bytes in an array of iovec structures.
+inline size_t TotalIovecLength(const struct iovec* iov, size_t iovcnt) {
+  size_t length = 0;
+  if (iov != NULL) {
+    for (size_t i = 0; i < iovcnt; ++i) {
+      length += iov[i].iov_len;
+    }
+  }
+  return length;
+}
+
+// IOVector is a helper class that makes it easier to work with POSIX vector I/O
+// struct. It is a thin wrapper by design and thus has no virtual functions and
+// all inlined methods. This class makes no assumptions about the ordering of
+// the pointer values of the blocks appended, it simply counts bytes when asked
+// to consume bytes.
+//
+// IOVector is a bookkeeping object that collects a description of buffers to
+// be read or written together and in order. It does not take ownership of the
+// blocks appended.
+//
+// Because it is used for scatter-gather operations, the order in which the
+// buffer blocks are added to the IOVector is important to the client. The
+// intended usage pattern is:
+//
+//   iovector.Append(p0, len0);
+//   ...
+//   iovector.Append(pn, lenn);
+//   int bytes_written = writev(fd, iovector.iovec(), iovector.Size());
+//   if (bytes_written > 0)
+//     iovector.Consume(bytes_written);
+//
+// The sequence is the same for readv, except that Consume() in this case is
+// used to change the IOVector to only keep track of description of blocks of
+// memory not yet written to.
+//
+// IOVector does not have any method to change the iovec entries that it
+// accumulates. This is due to the block merging nature of Append(): we'd like
+// to avoid accidentally change an entry that is assembled by two or more
+// Append()'s by simply an index access.
+//
+class NET_EXPORT_PRIVATE IOVector {
+ public:
+  // Provide a default constructor so it'll never be inhibited by adding other
+  // constructors.
+  IOVector();
+  ~IOVector();
+
+  // Provides a way to convert system call-like iovec representation to
+  // IOVector.
+  void AppendIovec(const struct iovec* iov, size_t iovcnt) {
+    for (size_t i = 0; i < iovcnt; ++i)
+      Append(static_cast<char*>(iov[i].iov_base), iov[i].iov_len);
+  }
+
+  // Appends at most max_bytes from iovec to the IOVector.
+  size_t AppendIovecAtMostBytes(const struct iovec* iov,
+                                size_t iovcnt,
+                                size_t max_bytes) {
+    size_t bytes_appended = 0;
+    for (size_t i = 0; i < iovcnt && max_bytes > 0; ++i) {
+      const size_t length = std::min(max_bytes, iov[i].iov_len);
+      Append(static_cast<char*>(iov[i].iov_base), length);
+      max_bytes -= length;
+      bytes_appended += length;
+    }
+    return bytes_appended;
+  }
+
+  // Append another block to the IOVector. Since IOVector can be used for read
+  // and write, it always takes char*. Clients that writes will need to cast
+  // away the constant of the pointer before appending a block.
+  void Append(char* buffer, size_t length) {
+    if (buffer != NULL && length > 0) {
+      if (iovec_.size() > 0) {
+        struct iovec& last = iovec_.back();
+        // If the new block is contiguous with the last block, just extend.
+        if (static_cast<char*>(last.iov_base) + last.iov_len == buffer) {
+          last.iov_len += length;
+          return;
+        }
+      }
+      struct iovec tmp = {buffer, length};
+      iovec_.push_back(tmp);
+    }
+  }
+
+  // Same as Append, but doesn't do the tail merge optimization.
+  // Intended for testing.
+  void AppendNoCoalesce(char* buffer, size_t length) {
+    if (buffer != NULL && length > 0) {
+      struct iovec tmp = {buffer, length};
+      iovec_.push_back(tmp);
+    }
+  }
+
+  // Remove a number of bytes from the beginning of the IOVector. Since vector
+  // I/O operations always occur at the beginning of the block list, a method
+  // to remove bytes at the end is not provided.
+  // It returns the number of bytes actually consumed (it'll only be smaller
+  // than the requested number if the IOVector contains less data).
+  size_t Consume(size_t length) {
+    if (length == 0) return 0;
+
+    size_t bytes_to_consume = length;
+    std::vector<struct iovec>::iterator iter = iovec_.begin();
+    std::vector<struct iovec>::iterator end = iovec_.end();
+    for (; iter < end && bytes_to_consume >= iter->iov_len; ++iter) {
+      bytes_to_consume -= iter->iov_len;
+    }
+    iovec_.erase(iovec_.begin(), iter);
+    if (iovec_.size() > 0 && bytes_to_consume != 0) {
+      iovec_[0].iov_base =
+          static_cast<char*>(iovec_[0].iov_base) + bytes_to_consume;
+      iovec_[0].iov_len -= bytes_to_consume;
+      return length;
+    }
+    if (iovec_.size() == 0 && bytes_to_consume > 0) {
+      LOG(DFATAL) << "Attempting to consume " << bytes_to_consume
+                  << " non-existent bytes.";
+    }
+    // At this point bytes_to_consume is the number of wanted bytes left over
+    // after walking through all the iovec entries.
+    return length - bytes_to_consume;
+  }
+
+  // TODO(joechan): If capacity is large, swap out for a blank one.
+  // Clears the IOVector object to contain no blocks.
+  void Clear() { iovec_.clear(); }
+
+  // Swap the guts of two IOVector.
+  void Swap(IOVector* other) { iovec_.swap(other->iovec_); }
+
+  // Returns the number of valid blocks in the IOVector (not the number of
+  // bytes).
+  size_t Size() const { return iovec_.size(); }
+
+  // Returns the total storage used by the IOVector in number of blocks (not
+  // the number of bytes).
+  size_t Capacity() const { return iovec_.capacity(); }
+
+  // Returns true if there are no blocks in the IOVector.
+  bool Empty() const { return iovec_.empty(); }
+
+  // Returns the pointer to the beginning of the iovec to be used for vector
+  // I/O operations. If the IOVector has no blocks appened, this function
+  // returns NULL.
+  struct iovec* iovec() { return !Empty() ? &iovec_[0] : NULL; }
+
+  // Const version.
+  const struct iovec* iovec() const { return !Empty() ? &iovec_[0] : NULL; }
+
+  // Returns a pointer to one past the last byte of the last block. If the
+  // IOVector is empty, NULL is returned.
+  const char* LastBlockEnd() const {
+    return iovec_.size() > 0 ?
+        static_cast<char *>(iovec_.back().iov_base) + iovec_.back().iov_len :
+        NULL;
+  }
+
+  // Returns the total number of bytes in the IOVector.
+  size_t TotalBufferSize() const { return TotalIovecLength(iovec(), Size()); }
+
+  void Resize(size_t count) {
+    iovec_.resize(count);
+  }
+
+ private:
+  std::vector<struct iovec> iovec_;
+
+  // IOVector has value-semantics; copy and assignment are allowed.
+  // This class does not explicitly define copy/move constructors or the
+  // assignment operator to preserve compiler-generated copy/move constructors
+  // and assignment operators. Note that since IOVector does not own the
+  // actual buffers that the struct iovecs point to, copies and assignments
+  // result in a shallow copy of the buffers; resulting IOVectors will point
+  // to the same copy of the underlying data.
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_IOVECTOR_H_
diff --git a/net/quic/iovector_test.cc b/net/quic/iovector_test.cc
new file mode 100644
index 0000000..e6cc058
--- /dev/null
+++ b/net/quic/iovector_test.cc
@@ -0,0 +1,283 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/iovector.h"
+
+#include <string.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+const char* const test_data[] = {
+  "test string 1, a medium size one.",
+  "test string2",
+  "test string      3, a looooooooooooong loooooooooooooooong string"
+};
+
+TEST(IOVectorTest, CopyConstructor) {
+  IOVector iov1;
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    iov1.Append(const_cast<char*>(test_data[i]), strlen(test_data[i]));
+  }
+  IOVector iov2 = iov1;
+  EXPECT_EQ(iov2.Size(), iov1.Size());
+  for (size_t i = 0; i < iov2.Size(); ++i) {
+    EXPECT_TRUE(iov2.iovec()[i].iov_base == iov1.iovec()[i].iov_base);
+    EXPECT_EQ(iov2.iovec()[i].iov_len, iov1.iovec()[i].iov_len);
+  }
+  EXPECT_EQ(iov2.TotalBufferSize(), iov1.TotalBufferSize());
+}
+
+TEST(IOVectorTest, AssignmentOperator) {
+  IOVector iov1;
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    iov1.Append(const_cast<char*>(test_data[i]), strlen(test_data[i]));
+  }
+  IOVector iov2;
+  iov2.Append(const_cast<char*>("ephemeral string"), 16);
+  // The following assignment results in a shallow copy;
+  // both IOVectors point to the same underlying data.
+  iov2 = iov1;
+  EXPECT_EQ(iov2.Size(), iov1.Size());
+  for (size_t i = 0; i < iov2.Size(); ++i) {
+    EXPECT_TRUE(iov2.iovec()[i].iov_base == iov1.iovec()[i].iov_base);
+    EXPECT_EQ(iov2.iovec()[i].iov_len, iov1.iovec()[i].iov_len);
+  }
+  EXPECT_EQ(iov2.TotalBufferSize(), iov1.TotalBufferSize());
+}
+
+TEST(IOVectorTest, Append) {
+  IOVector iov;
+  int length = 0;
+  const struct iovec* iov2 = iov.iovec();
+
+  ASSERT_EQ(0u, iov.Size());
+  ASSERT_TRUE(iov2 == nullptr);
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const int str_len = strlen(test_data[i]);
+    const int append_len = str_len / 2;
+    // This should append a new block
+    iov.Append(const_cast<char*>(test_data[i]), append_len);
+    length += append_len;
+    ASSERT_EQ(i + 1, static_cast<size_t>(iov.Size()));
+    ASSERT_TRUE(iov.LastBlockEnd() == test_data[i] + append_len);
+    // This should just lengthen the existing block.
+    iov.Append(const_cast<char*>(test_data[i] + append_len),
+               str_len - append_len);
+    length += (str_len - append_len);
+    ASSERT_EQ(i + 1, static_cast<size_t>(iov.Size()));
+    ASSERT_TRUE(iov.LastBlockEnd() == test_data[i] + str_len);
+  }
+
+  iov2 = iov.iovec();
+  ASSERT_TRUE(iov2 != nullptr);
+  for (size_t i = 0; i < iov.Size(); ++i) {
+    ASSERT_TRUE(test_data[i] == iov2[i].iov_base);
+    ASSERT_EQ(strlen(test_data[i]), iov2[i].iov_len);
+  }
+}
+
+TEST(IOVectorTest, AppendIovec) {
+  IOVector iov;
+  const struct iovec test_iov[] = {
+    {const_cast<char*>("foo"), 3},
+    {const_cast<char*>("bar"), 3},
+    {const_cast<char*>("buzzzz"), 6}
+  };
+  iov.AppendIovec(test_iov, ARRAYSIZE_UNSAFE(test_iov));
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_iov); ++i) {
+    EXPECT_EQ(test_iov[i].iov_base, iov.iovec()[i].iov_base);
+    EXPECT_EQ(test_iov[i].iov_len, iov.iovec()[i].iov_len);
+  }
+
+  // Test AppendIovecAtMostBytes.
+  iov.Clear();
+  // Stop in the middle of a block.
+  EXPECT_EQ(5u, iov.AppendIovecAtMostBytes(test_iov, ARRAYSIZE_UNSAFE(test_iov),
+                                           5));
+  EXPECT_EQ(5u, iov.TotalBufferSize());
+  iov.Append(static_cast<char*>(test_iov[1].iov_base) + 2, 1);
+  // Make sure the boundary case, where max_bytes == size of block also works.
+  EXPECT_EQ(6u, iov.AppendIovecAtMostBytes(&test_iov[2], 1, 6));
+  ASSERT_LE(ARRAYSIZE_UNSAFE(test_iov), static_cast<size_t>(iov.Size()));
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_iov); ++i) {
+    EXPECT_EQ(test_iov[i].iov_base, iov.iovec()[i].iov_base);
+    EXPECT_EQ(test_iov[i].iov_len, iov.iovec()[i].iov_len);
+  }
+}
+
+TEST(IOVectorTest, ConsumeHalfBlocks) {
+  IOVector iov;
+  int length = 0;
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const int str_len = strlen(test_data[i]);
+    iov.Append(const_cast<char*>(test_data[i]), str_len);
+    length += str_len;
+  }
+  const char* endp = iov.LastBlockEnd();
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const struct iovec* iov2 = iov.iovec();
+    const size_t str_len = strlen(test_data[i]);
+    size_t tmp = str_len / 2;
+
+    ASSERT_TRUE(iov2 != nullptr);
+    ASSERT_TRUE(iov2[0].iov_base == test_data[i]);
+    ASSERT_EQ(str_len, iov2[0].iov_len);
+
+    // Consume half of the first block.
+    size_t consumed = iov.Consume(tmp);
+    ASSERT_EQ(tmp, consumed);
+    ASSERT_EQ(ARRAYSIZE_UNSAFE(test_data) - i, static_cast<size_t>(iov.Size()));
+    iov2 = iov.iovec();
+    ASSERT_TRUE(iov2 != nullptr);
+    ASSERT_TRUE(iov2[0].iov_base == test_data[i] + tmp);
+    ASSERT_EQ(iov2[0].iov_len, str_len - tmp);
+
+    // Consume the rest of the first block
+    consumed = iov.Consume(str_len - tmp);
+    ASSERT_EQ(str_len - tmp, consumed);
+    ASSERT_EQ(ARRAYSIZE_UNSAFE(test_data) - i - 1,
+              static_cast<size_t>(iov.Size()));
+    iov2 = iov.iovec();
+    if (iov.Size() > 0) {
+      ASSERT_TRUE(iov2 != nullptr);
+      ASSERT_TRUE(iov.LastBlockEnd() == endp);
+    } else {
+      ASSERT_TRUE(iov2 == nullptr);
+      ASSERT_TRUE(iov.LastBlockEnd() == nullptr);
+    }
+  }
+}
+
+TEST(IOVectorTest, ConsumeTwoAndHalfBlocks) {
+  IOVector iov;
+  int length = 0;
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const int str_len = strlen(test_data[i]);
+    iov.Append(const_cast<char*>(test_data[i]), str_len);
+    length += str_len;
+  }
+  const size_t last_len = strlen(test_data[ARRAYSIZE_UNSAFE(test_data) - 1]);
+  const size_t half_len = last_len / 2;
+
+  const char* endp = iov.LastBlockEnd();
+  size_t consumed = iov.Consume(length - half_len);
+  ASSERT_EQ(length - half_len, consumed);
+  const struct iovec* iov2 = iov.iovec();
+  ASSERT_TRUE(iov2 != nullptr);
+  ASSERT_EQ(1u, iov.Size());
+  ASSERT_TRUE(iov2[0].iov_base ==
+              test_data[ARRAYSIZE_UNSAFE(test_data) - 1] + last_len - half_len);
+  ASSERT_EQ(half_len, iov2[0].iov_len);
+  ASSERT_TRUE(iov.LastBlockEnd() == endp);
+
+  consumed = iov.Consume(half_len);
+  ASSERT_EQ(half_len, consumed);
+  iov2 = iov.iovec();
+  ASSERT_EQ(0u, iov.Size());
+  ASSERT_TRUE(iov2 == nullptr);
+  ASSERT_TRUE(iov.LastBlockEnd() == nullptr);
+}
+
+TEST(IOVectorTest, ConsumeTooMuch) {
+  IOVector iov;
+  int length = 0;
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const int str_len = strlen(test_data[i]);
+    iov.Append(const_cast<char*>(test_data[i]), str_len);
+    length += str_len;
+  }
+
+  int consumed = 0;
+  consumed = iov.Consume(length);
+  // TODO(rtenneti): enable when chromium supports EXPECT_DFATAL.
+  /*
+  EXPECT_DFATAL(
+      {consumed = iov.Consume(length + 1);},
+      "Attempting to consume 1 non-existent bytes.");
+  */
+  ASSERT_EQ(length, consumed);
+  const struct iovec* iov2 = iov.iovec();
+  ASSERT_EQ(0u, iov.Size());
+  ASSERT_TRUE(iov2 == nullptr);
+  ASSERT_TRUE(iov.LastBlockEnd() == nullptr);
+}
+
+TEST(IOVectorTest, Clear) {
+  IOVector iov;
+  int length = 0;
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
+    const int str_len = strlen(test_data[i]);
+    iov.Append(const_cast<char*>(test_data[i]), str_len);
+    length += str_len;
+  }
+  const struct iovec* iov2 = iov.iovec();
+  ASSERT_TRUE(iov2 != nullptr);
+  ASSERT_EQ(ARRAYSIZE_UNSAFE(test_data), static_cast<size_t>(iov.Size()));
+
+  iov.Clear();
+  iov2 = iov.iovec();
+  ASSERT_EQ(0u, iov.Size());
+  ASSERT_TRUE(iov2 == nullptr);
+}
+
+TEST(IOVectorTest, Capacity) {
+  IOVector iov;
+  // Note: IOVector merges adjacent Appends() into a single iov.
+  // Therefore, if we expect final size of iov to be 3, we must insure
+  // that the items we are appending are not adjacent. To achieve that
+  // we use use an array (a[1] provides a buffer between a[0] and b[0],
+  // and makes them non-adjacent).
+  char a[2], b[2], c[2];
+  iov.Append(&a[0], 1);
+  iov.Append(&b[0], 1);
+  iov.Append(&c[0], 1);
+  ASSERT_EQ(3u, iov.Size());
+  size_t capacity = iov.Capacity();
+  EXPECT_LE(iov.Size(), capacity);
+  iov.Consume(2);
+  // The capacity should not have changed.
+  EXPECT_EQ(capacity, iov.Capacity());
+}
+
+TEST(IOVectorTest, Swap) {
+  IOVector iov1, iov2;
+  // See IOVector merge comment above.
+  char a[2], b[2], c[2], d[2], e[2];
+  iov1.Append(&a[0], 1);
+  iov1.Append(&b[0], 1);
+
+  iov2.Append(&c[0], 1);
+  iov2.Append(&d[0], 1);
+  iov2.Append(&e[0], 1);
+  iov1.Swap(&iov2);
+
+  ASSERT_EQ(3u, iov1.Size());
+  EXPECT_EQ(&c[0], iov1.iovec()[0].iov_base);
+  EXPECT_EQ(1u, iov1.iovec()[0].iov_len);
+  EXPECT_EQ(&d[0], iov1.iovec()[1].iov_base);
+  EXPECT_EQ(1u, iov1.iovec()[1].iov_len);
+  EXPECT_EQ(&e[0], iov1.iovec()[2].iov_base);
+  EXPECT_EQ(1u, iov1.iovec()[2].iov_len);
+
+  ASSERT_EQ(2u, iov2.Size());
+  EXPECT_EQ(&a[0], iov2.iovec()[0].iov_base);
+  EXPECT_EQ(1u, iov2.iovec()[0].iov_len);
+  EXPECT_EQ(&b[0], iov2.iovec()[1].iov_base);
+  EXPECT_EQ(1u, iov2.iovec()[1].iov_len);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/port_suggester.cc b/net/quic/port_suggester.cc
new file mode 100644
index 0000000..6b7940e
--- /dev/null
+++ b/net/quic/port_suggester.cc
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/port_suggester.h"
+
+#include "base/logging.h"
+#include "net/base/host_port_pair.h"
+
+namespace net {
+
+PortSuggester::PortSuggester(const HostPortPair& server, uint64 seed)
+    : call_count_(0),
+      previous_suggestion_(-1) {
+  unsigned char hash_bytes[base::kSHA1Length];
+  base::SHA1HashBytes(
+      reinterpret_cast<const unsigned char*>(server.host().data()),
+      server.host().length(), hash_bytes);
+  COMPILE_ASSERT(sizeof(seed_) < sizeof(hash_bytes), seed_larger_than_hash);
+  memcpy(&seed_, hash_bytes, sizeof(seed_));
+  seed_ ^= seed ^ server.port();
+}
+
+int PortSuggester::SuggestPort(int min, int max) {
+  // Sometimes our suggestion can't be used, so we ensure that if additional
+  // calls are made, then each call (probably) provides a new suggestion.
+  if (++call_count_ > 1) {
+    // Evolve the seed.
+    unsigned char hash_bytes[base::kSHA1Length];
+    base::SHA1HashBytes(reinterpret_cast<const unsigned char *>(&seed_),
+                        sizeof(seed_), hash_bytes);
+    memcpy(&seed_, hash_bytes, sizeof(seed_));
+  }
+  DCHECK_LE(min, max);
+  DCHECK_GT(min, 0);
+  int range = max - min + 1;
+  // Ports (and hence the extent of the |range|) are generally under 2^16, so
+  // the tiny non-uniformity in the pseudo-random distribution is not
+  // significant.
+  previous_suggestion_ = static_cast<int>(seed_ % range) + min;
+  return previous_suggestion_;
+}
+
+int PortSuggester::previous_suggestion() const {
+  DCHECK_LT(0u, call_count_);
+  return previous_suggestion_;
+}
+
+}  // namespace net
diff --git a/net/quic/port_suggester.h b/net/quic/port_suggester.h
new file mode 100644
index 0000000..0074f7c
--- /dev/null
+++ b/net/quic/port_suggester.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_PORT_SUGGESTER_H_
+#define NET_QUIC_PORT_SUGGESTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/sha1.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class HostPortPair;
+
+// We provide a pseudo-random number generator that is always seeded the same
+// way for a given destination host-port pair.  The generator is used to
+// consistently suggest (for that host-port pair) an ephemeral source port,
+// and hence increase the likelihood that a server's load balancer will direct
+// a repeated connection to the same server (with QUIC, further increasing the
+// chance of connection establishment with 0-RTT).
+class NET_EXPORT_PRIVATE PortSuggester
+    : public base::RefCounted<PortSuggester> {
+ public:
+  PortSuggester(const HostPortPair& server, uint64 seed);
+
+  // Generate a pseudo-random int in the inclusive range from |min| to |max|.
+  // Will (probably) return different numbers when called repeatedly.
+  int SuggestPort(int min, int max);
+
+  uint32 call_count() const { return call_count_; }
+  int previous_suggestion() const;
+
+ private:
+  friend class base::RefCounted<PortSuggester>;
+
+  virtual ~PortSuggester() {}
+
+  // We maintain the first 8 bytes of a hash as our seed_ state.
+  uint64 seed_;
+  uint32 call_count_;  // Number of suggestions made.
+  int previous_suggestion_;
+
+  DISALLOW_COPY_AND_ASSIGN(PortSuggester);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_PORT_SUGGESTER_H_
diff --git a/net/quic/port_suggester_unittest.cc b/net/quic/port_suggester_unittest.cc
new file mode 100644
index 0000000..add5258
--- /dev/null
+++ b/net/quic/port_suggester_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/port_suggester.h"
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "net/base/host_port_pair.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class PortSuggesterTest : public ::testing::Test {
+ protected:
+  PortSuggesterTest()
+      : entropy_(1345689),
+        min_ephemeral_port_(1025),
+        max_ephemeral_port_(65535) {
+  }
+
+  uint64 entropy_;
+  int min_ephemeral_port_;
+  int max_ephemeral_port_;
+};
+
+TEST_F(PortSuggesterTest, SmallRangeTest) {
+  // When the range is small (one wide), we always get that as our answer.
+  scoped_refptr<PortSuggester> port_suggester =
+      new PortSuggester(HostPortPair("www.example.com", 443), entropy_);
+  // Test this for a few different (small) ranges.
+  for (int port = 2000; port < 2010; ++port) {
+    // Use |port| for both |min| and |max| delimiting the suggestion range.
+    EXPECT_EQ(port, port_suggester->SuggestPort(port, port));
+    EXPECT_EQ(port, port_suggester->previous_suggestion());
+  }
+}
+
+TEST_F(PortSuggesterTest, SuggestAllPorts) {
+  // We should eventually fill out any range, but we'll just ensure that we
+  // fill out a small range of ports.
+  scoped_refptr<PortSuggester> port_suggester =
+      new PortSuggester(HostPortPair("www.example.com", 443), entropy_);
+  std::set<int> ports;
+  const uint32 port_range = 20;
+  const int insertion_limit = 200;  // We should be done by then.
+  for (int i = 0; i < insertion_limit; ++i) {
+    ports.insert(port_suggester->SuggestPort(min_ephemeral_port_,
+        min_ephemeral_port_ + port_range - 1));
+    if (ports.size() == port_range) {
+      break;
+    }
+  }
+  EXPECT_EQ(port_range, ports.size());
+}
+
+TEST_F(PortSuggesterTest, AvoidDuplication) {
+  // When the range is large, duplicates are rare, but we'll ask for a few
+  // suggestions and make sure they are unique.
+  scoped_refptr<PortSuggester> port_suggester =
+      new PortSuggester(HostPortPair("www.example.com", 80), entropy_);
+  std::set<int> ports;
+  const size_t port_count = 200;
+  for (size_t i = 0; i < port_count; ++i) {
+    ports.insert(port_suggester->SuggestPort(min_ephemeral_port_,
+                                             max_ephemeral_port_));
+  }
+  EXPECT_EQ(port_suggester->call_count(), port_count);
+  EXPECT_EQ(port_count, ports.size());
+}
+
+TEST_F(PortSuggesterTest, ConsistentPorts) {
+  // For given hostname, port, and entropy, we should always get the same
+  // suggestions.
+  scoped_refptr<PortSuggester> port_suggester1 =
+      new PortSuggester(HostPortPair("www.example.com", 443), entropy_);
+  scoped_refptr<PortSuggester> port_suggester2 =
+      new PortSuggester(HostPortPair("www.example.com", 443), entropy_);
+  for (int test_count = 20; test_count > 0; --test_count) {
+    EXPECT_EQ(port_suggester1->SuggestPort(min_ephemeral_port_,
+                                           min_ephemeral_port_),
+              port_suggester2->SuggestPort(min_ephemeral_port_,
+                                           min_ephemeral_port_));
+  }
+}
+
+TEST_F(PortSuggesterTest, DifferentHostPortEntropy) {
+  // When we have different hosts, port, or entropy, we probably won't collide.
+  scoped_refptr<PortSuggester> port_suggester[] = {
+      new PortSuggester(HostPortPair("www.example.com", 80), entropy_),
+      new PortSuggester(HostPortPair("www.example.ORG", 80), entropy_),
+      new PortSuggester(HostPortPair("www.example.com", 443), entropy_),
+      new PortSuggester(HostPortPair("www.example.com", 80), entropy_ + 123456),
+  };
+
+  std::set<int> ports;
+  const int port_count = 40;
+  size_t insertion_count = 0;
+  for (size_t j = 0; j < arraysize(port_suggester); ++j) {
+    for (int i = 0; i < port_count; ++i) {
+     ports.insert(port_suggester[j]->SuggestPort(min_ephemeral_port_,
+                                                 max_ephemeral_port_));
+     ++insertion_count;
+    }
+  }
+  EXPECT_EQ(insertion_count, ports.size());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_ack_notifier.cc b/net/quic/quic_ack_notifier.cc
new file mode 100644
index 0000000..3bd65e5
--- /dev/null
+++ b/net/quic/quic_ack_notifier.cc
@@ -0,0 +1,85 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_ack_notifier.h"
+
+#include <set>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+using base::hash_map;
+using std::make_pair;
+
+namespace net {
+
+QuicAckNotifier::PacketInfo::PacketInfo() : packet_payload_size(0) {
+}
+
+QuicAckNotifier::PacketInfo::PacketInfo(int payload_size)
+    : packet_payload_size(payload_size) {
+}
+
+QuicAckNotifier::DelegateInterface::DelegateInterface() {}
+
+QuicAckNotifier::DelegateInterface::~DelegateInterface() {}
+
+QuicAckNotifier::QuicAckNotifier(DelegateInterface* delegate)
+    : delegate_(delegate),
+      original_packet_count_(0),
+      original_byte_count_(0),
+      retransmitted_packet_count_(0),
+      retransmitted_byte_count_(0) {
+  DCHECK(delegate);
+}
+
+QuicAckNotifier::~QuicAckNotifier() {
+}
+
+void QuicAckNotifier::AddSequenceNumber(
+    const QuicPacketSequenceNumber& sequence_number,
+    int packet_payload_size) {
+  sequence_numbers_.insert(make_pair(sequence_number,
+                                     PacketInfo(packet_payload_size)));
+  ++original_packet_count_;
+  original_byte_count_ += packet_payload_size;
+}
+
+bool QuicAckNotifier::OnAck(QuicPacketSequenceNumber sequence_number,
+                            QuicTime::Delta delta_largest_observed) {
+  DCHECK(ContainsKey(sequence_numbers_, sequence_number));
+  sequence_numbers_.erase(sequence_number);
+  if (IsEmpty()) {
+    // We have seen all the sequence numbers we were waiting for, trigger
+    // callback notification.
+    delegate_->OnAckNotification(
+        original_packet_count_, original_byte_count_,
+        retransmitted_packet_count_, retransmitted_byte_count_,
+        delta_largest_observed);
+    return true;
+  }
+  return false;
+}
+
+void QuicAckNotifier::UpdateSequenceNumber(
+    QuicPacketSequenceNumber old_sequence_number,
+    QuicPacketSequenceNumber new_sequence_number) {
+  DCHECK(!ContainsKey(sequence_numbers_, new_sequence_number));
+
+  PacketInfo packet_info;
+  hash_map<QuicPacketSequenceNumber, PacketInfo>::iterator it =
+      sequence_numbers_.find(old_sequence_number);
+  if (it != sequence_numbers_.end()) {
+    packet_info = it->second;
+    sequence_numbers_.erase(it);
+  } else {
+    DLOG(DFATAL) << "Old sequence number not found.";
+  }
+
+  ++retransmitted_packet_count_;
+  retransmitted_byte_count_ += packet_info.packet_payload_size;
+  sequence_numbers_.insert(make_pair(new_sequence_number, packet_info));
+}
+
+};  // namespace net
diff --git a/net/quic/quic_ack_notifier.h b/net/quic/quic_ack_notifier.h
new file mode 100644
index 0000000..f19d0f8
--- /dev/null
+++ b/net/quic/quic_ack_notifier.h
@@ -0,0 +1,101 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_ACK_NOTIFIER_H_
+#define NET_QUIC_QUIC_ACK_NOTIFIER_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// Used to register with a QuicConnection for notification once a set of packets
+// have all been ACKed.
+// The connection informs this class of newly ACKed sequence numbers, and once
+// we have seen ACKs for all the sequence numbers we are interested in, we
+// trigger a call to a provided Closure.
+class NET_EXPORT_PRIVATE QuicAckNotifier {
+ public:
+ class NET_EXPORT_PRIVATE DelegateInterface
+     : public base::RefCounted<DelegateInterface> {
+   public:
+    DelegateInterface();
+    // Args:
+    //  num_original_packets - Number of packets in the original transmission.
+    //  num_original_bytes - Number of packets in the original transmission.
+    //  num_retransmitted_packets - Number of packets that had to be
+    //                              retransmitted.
+    //  num_retransmitted_bytes - Number of bytes that had to be retransmitted.
+    virtual void OnAckNotification(int num_original_packets,
+                                   int num_original_bytes,
+                                   int num_retransmitted_packets,
+                                   int num_retransmitted_bytes,
+                                   QuicTime::Delta delta_largest_observed) = 0;
+   protected:
+    friend class base::RefCounted<DelegateInterface>;
+
+    // Delegates are ref counted.
+    virtual ~DelegateInterface();
+  };
+
+  // QuicAckNotifier is expected to keep its own reference to the delegate.
+  explicit QuicAckNotifier(DelegateInterface* delegate);
+  virtual ~QuicAckNotifier();
+
+  // Register a sequence number that this AckNotifier should be interested in.
+  void AddSequenceNumber(const QuicPacketSequenceNumber& sequence_number,
+                         int packet_payload_size);
+
+  // Called by the QuicConnection on receipt of new ACK frame, with the sequence
+  // number referenced by the ACK frame.
+  // Deletes the matching sequence number from the stored set of sequence
+  // numbers. If this set is now empty, call the stored delegate's
+  // OnAckNotification method.
+  //
+  // Returns true if the provided sequence_number caused the delegate to be
+  // called, false otherwise.
+  bool OnAck(QuicPacketSequenceNumber sequence_number,
+             QuicTime::Delta delta_largest_observed);
+
+  bool IsEmpty() { return sequence_numbers_.empty(); }
+
+  // If a packet is retransmitted by the connection it will be sent with a
+  // different sequence number. Updates our internal set of sequence_numbers to
+  // track the latest number.
+  void UpdateSequenceNumber(QuicPacketSequenceNumber old_sequence_number,
+                            QuicPacketSequenceNumber new_sequence_number);
+
+ private:
+  struct PacketInfo {
+    PacketInfo();
+    explicit PacketInfo(int payload_size);
+
+    int packet_payload_size;
+  };
+
+  // The delegate's OnAckNotification() method will be called once we have been
+  // notified of ACKs for all the sequence numbers we are tracking.
+  // This is not owned by OnAckNotifier and must outlive it.
+  scoped_refptr<DelegateInterface> delegate_;
+
+  // Sequence numbers this notifier is waiting to hear about. The
+  // delegate will not be called until this is empty.
+  base::hash_map<QuicPacketSequenceNumber, PacketInfo> sequence_numbers_;
+
+  // Transmission and retransmission stats.
+  // Number of packets in the original transmission.
+  int original_packet_count_;
+  // Number of packets in the original transmission.
+  int original_byte_count_;
+  // Number of packets that had to be retransmitted.
+  int retransmitted_packet_count_;
+  // Number of bytes that had to be retransmitted.
+  int retransmitted_byte_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicAckNotifier);
+};
+
+};  // namespace net
+
+#endif  // NET_QUIC_QUIC_ACK_NOTIFIER_H_
diff --git a/net/quic/quic_ack_notifier_manager.cc b/net/quic/quic_ack_notifier_manager.cc
new file mode 100644
index 0000000..1d9ac9a
--- /dev/null
+++ b/net/quic/quic_ack_notifier_manager.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_ack_notifier_manager.h"
+
+#include <stddef.h>
+#include <list>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/stl_util.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+AckNotifierManager::AckNotifierManager() {}
+
+AckNotifierManager::~AckNotifierManager() {
+  STLDeleteElements(&ack_notifiers_);
+}
+
+void AckNotifierManager::OnPacketAcked(
+    QuicPacketSequenceNumber sequence_number,
+    QuicTime::Delta delta_largest_observed) {
+  // Inform all the registered AckNotifiers of the new ACK.
+  AckNotifierMap::iterator map_it = ack_notifier_map_.find(sequence_number);
+  if (map_it == ack_notifier_map_.end()) {
+    // No AckNotifier is interested in this sequence number.
+    return;
+  }
+
+  // One or more AckNotifiers are registered as interested in this sequence
+  // number. Iterate through them and call OnAck on each.
+  for (AckNotifierSet::iterator set_it = map_it->second.begin();
+       set_it != map_it->second.end(); ++set_it) {
+    QuicAckNotifier* ack_notifier = *set_it;
+    ack_notifier->OnAck(sequence_number, delta_largest_observed);
+
+    // If this has resulted in an empty AckNotifer, erase it.
+    if (ack_notifier->IsEmpty()) {
+      delete ack_notifier;
+      ack_notifiers_.erase(ack_notifier);
+    }
+  }
+
+  // Remove the sequence number from the map as we have notified all the
+  // registered AckNotifiers, and we won't see it again.
+  ack_notifier_map_.erase(map_it);
+}
+
+void AckNotifierManager::UpdateSequenceNumber(
+    QuicPacketSequenceNumber old_sequence_number,
+    QuicPacketSequenceNumber new_sequence_number) {
+  AckNotifierMap::iterator map_it = ack_notifier_map_.find(old_sequence_number);
+  if (map_it != ack_notifier_map_.end()) {
+    // We will add an entry to the map for the new sequence number, and move
+    // the
+    // list of AckNotifiers over.
+    AckNotifierSet new_set;
+    for (AckNotifierSet::iterator notifier_it = map_it->second.begin();
+         notifier_it != map_it->second.end(); ++notifier_it) {
+      (*notifier_it)
+          ->UpdateSequenceNumber(old_sequence_number, new_sequence_number);
+      new_set.insert(*notifier_it);
+    }
+    ack_notifier_map_[new_sequence_number] = new_set;
+    ack_notifier_map_.erase(map_it);
+  }
+}
+
+void AckNotifierManager::OnSerializedPacket(
+    const SerializedPacket& serialized_packet) {
+  // Run through all the frames and if any of them are stream frames and have
+  // an AckNotifier registered, then inform the AckNotifier that it should be
+  // interested in this packet's sequence number.
+
+  RetransmittableFrames* frames = serialized_packet.retransmittable_frames;
+
+  // AckNotifiers can only be attached to retransmittable frames.
+  if (!frames) {
+    return;
+  }
+
+  for (QuicFrames::const_iterator it = frames->frames().begin();
+       it != frames->frames().end(); ++it) {
+    if (it->type == STREAM_FRAME && it->stream_frame->notifier != nullptr) {
+      QuicAckNotifier* notifier = it->stream_frame->notifier;
+
+      // The AckNotifier needs to know it is tracking this packet's sequence
+      // number.
+      notifier->AddSequenceNumber(serialized_packet.sequence_number,
+                                  serialized_packet.packet->length());
+
+      // Update the mapping in the other direction, from sequence
+      // number to AckNotifier.
+      ack_notifier_map_[serialized_packet.sequence_number].insert(notifier);
+
+      // Take ownership of the AckNotifier.
+      ack_notifiers_.insert(notifier);
+    }
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_ack_notifier_manager.h b/net/quic/quic_ack_notifier_manager.h
new file mode 100644
index 0000000..bf5b345
--- /dev/null
+++ b/net/quic/quic_ack_notifier_manager.h
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_ACK_NOTIFIER_MANAGER_H_
+#define NET_QUIC_QUIC_ACK_NOTIFIER_MANAGER_H_
+
+#include <map>
+
+#include "base/containers/hash_tables.h"
+#include "net/quic/quic_protocol.h"
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template<>
+struct hash<net::QuicAckNotifier*> {
+  std::size_t operator()(const net::QuicAckNotifier* ptr) const {
+    return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+  }
+};
+}
+#endif
+
+namespace net {
+
+class QuicAckNotifier;
+
+// The AckNotifierManager is used by the QuicSentPacketManager to keep track of
+// all the AckNotifiers currently active. It owns the AckNotifiers which it gets
+// from the serialized packets passed into OnSerializedPacket. It maintains both
+// a set of AckNotifiers and a map from sequence number to AckNotifier the sake
+// of efficiency - we can quickly check the map to see if any AckNotifiers are
+// interested in a given sequence number.
+class NET_EXPORT_PRIVATE AckNotifierManager {
+ public:
+  AckNotifierManager();
+  virtual ~AckNotifierManager();
+
+  // Called when the connection receives a new AckFrame.  If |sequence_number|
+  // exists in ack_notifier_map_ then the corresponding AckNotifiers will have
+  // their OnAck method called.
+  void OnPacketAcked(QuicPacketSequenceNumber sequence_number,
+                     QuicTime::Delta delta_largest_observed);
+
+  // If a packet has been retransmitted with a new sequence number, then this
+  // will be called. It updates the mapping in ack_notifier_map_, and also
+  // updates the internal set of sequence numbers in each matching AckNotifier.
+  void UpdateSequenceNumber(QuicPacketSequenceNumber old_sequence_number,
+                            QuicPacketSequenceNumber new_sequence_number);
+
+  // This is called after a packet has been serialized, is ready to be sent, and
+  // contains retransmittable frames (which may have associated AckNotifiers).
+  // If any of the retransmittable frames included in |serialized_packet| have
+  // AckNotifiers registered, then add them to our internal map and additionally
+  // inform the AckNotifier of the sequence number which it should track.
+  void OnSerializedPacket(const SerializedPacket& serialized_packet);
+
+ private:
+  typedef base::hash_set<QuicAckNotifier*> AckNotifierSet;
+  typedef std::map<QuicPacketSequenceNumber, AckNotifierSet> AckNotifierMap;
+
+  // On every ACK frame received by the connection, all the ack_notifiers_ will
+  // be told which sequeunce numbers were ACKed.
+  // Once a given QuicAckNotifier has seen all the sequence numbers it is
+  // interested in, it will be deleted, and removed from this set.
+  // Owns the AckNotifiers in this set.
+  AckNotifierSet ack_notifiers_;
+
+  // Maps from sequence number to the AckNotifiers which are registered
+  // for that sequence number. On receipt of an ACK for a given sequence
+  // number, call OnAck for all mapped AckNotifiers.
+  // Does not own the AckNotifiers.
+  AckNotifierMap ack_notifier_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(AckNotifierManager);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_ACK_NOTIFIER_MANAGER_H_
diff --git a/net/quic/quic_ack_notifier_test.cc b/net/quic/quic_ack_notifier_test.cc
new file mode 100644
index 0000000..63bc254
--- /dev/null
+++ b/net/quic/quic_ack_notifier_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_ack_notifier.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicAckNotifierTest : public ::testing::Test {
+ protected:
+  QuicAckNotifierTest() : zero_(QuicTime::Delta::Zero()) {}
+
+  virtual void SetUp() {
+    delegate_ = new MockAckNotifierDelegate;
+    notifier_.reset(new QuicAckNotifier(delegate_));
+
+    notifier_->AddSequenceNumber(26, 100);
+    notifier_->AddSequenceNumber(99, 20);
+    notifier_->AddSequenceNumber(1234, 3);
+  }
+
+  MockAckNotifierDelegate* delegate_;
+  scoped_ptr<QuicAckNotifier> notifier_;
+  QuicTime::Delta zero_;
+};
+
+// Should trigger callback when we receive acks for all the registered seqnums.
+TEST_F(QuicAckNotifierTest, TriggerCallback) {
+  EXPECT_CALL(*delegate_, OnAckNotification(3, 123, 0, 0, zero_)).Times(1);
+  EXPECT_FALSE(notifier_->OnAck(26, zero_));
+  EXPECT_FALSE(notifier_->OnAck(99, zero_));
+  EXPECT_TRUE(notifier_->OnAck(1234, zero_));
+}
+
+// Should not trigger callback if we never provide all the seqnums.
+TEST_F(QuicAckNotifierTest, DoesNotTrigger) {
+  // Should not trigger callback as not all packets have been seen.
+  EXPECT_CALL(*delegate_, OnAckNotification(_, _, _, _, _)).Times(0);
+  EXPECT_FALSE(notifier_->OnAck(26, zero_));
+  EXPECT_FALSE(notifier_->OnAck(99, zero_));
+}
+
+// Should trigger even after updating sequence numbers and receiving ACKs for
+// new sequeunce numbers.
+TEST_F(QuicAckNotifierTest, UpdateSeqNums) {
+  // Update a couple of the sequence numbers (i.e. retransmitted packets)
+  notifier_->UpdateSequenceNumber(99, 3000);
+  notifier_->UpdateSequenceNumber(1234, 3001);
+
+  EXPECT_CALL(*delegate_, OnAckNotification(3, 123, 2, 20 + 3, _)).Times(1);
+  EXPECT_FALSE(notifier_->OnAck(26, zero_));    // original
+  EXPECT_FALSE(notifier_->OnAck(3000, zero_));  // updated
+  EXPECT_TRUE(notifier_->OnAck(3001, zero_));   // updated
+}
+
+// Make sure the delegate is called with the delta time from the last ACK.
+TEST_F(QuicAckNotifierTest, DeltaTime) {
+  const QuicTime::Delta first_delta = QuicTime::Delta::FromSeconds(5);
+  const QuicTime::Delta second_delta = QuicTime::Delta::FromSeconds(33);
+  const QuicTime::Delta third_delta = QuicTime::Delta::FromSeconds(10);
+
+  EXPECT_CALL(*delegate_,
+              OnAckNotification(3, 123, 0, 0, third_delta))
+      .Times(1);
+  EXPECT_FALSE(notifier_->OnAck(26, first_delta));
+  EXPECT_FALSE(notifier_->OnAck(99, second_delta));
+  EXPECT_TRUE(notifier_->OnAck(1234, third_delta));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_address_mismatch.cc b/net/quic/quic_address_mismatch.cc
new file mode 100644
index 0000000..96aaef4
--- /dev/null
+++ b/net/quic/quic_address_mismatch.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_address_mismatch.h"
+
+#include "base/logging.h"
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+
+int GetAddressMismatch(const IPEndPoint& first_address,
+                       const IPEndPoint& second_address) {
+  if (first_address.address().empty() || second_address.address().empty()) {
+    return -1;
+  }
+  IPAddressNumber first_ip_address = first_address.address();
+  if (IsIPv4Mapped(first_ip_address)) {
+    first_ip_address = ConvertIPv4MappedToIPv4(first_ip_address);
+  }
+  IPAddressNumber second_ip_address = second_address.address();
+  if (IsIPv4Mapped(second_ip_address)) {
+    second_ip_address = ConvertIPv4MappedToIPv4(second_ip_address);
+  }
+
+  int sample;
+  if (first_ip_address != second_ip_address) {
+    sample = QUIC_ADDRESS_MISMATCH_BASE;
+  } else if (first_address.port() != second_address.port()) {
+    sample = QUIC_PORT_MISMATCH_BASE;
+  } else {
+    sample = QUIC_ADDRESS_AND_PORT_MATCH_BASE;
+  }
+
+  // Add an offset to |sample|:
+  //   V4_V4: add 0
+  //   V6_V6: add 1
+  //   V4_V6: add 2
+  //   V6_V4: add 3
+  bool first_ipv4 = (first_ip_address.size() == kIPv4AddressSize);
+  bool second_ipv4 = (second_ip_address.size() == kIPv4AddressSize);
+  if (first_ipv4 != second_ipv4) {
+    CHECK_EQ(sample, QUIC_ADDRESS_MISMATCH_BASE);
+    sample += 2;
+  }
+  if (!first_ipv4) {
+    sample += 1;
+  }
+  return sample;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_address_mismatch.h b/net/quic/quic_address_mismatch.h
new file mode 100644
index 0000000..3179c20
--- /dev/null
+++ b/net/quic/quic_address_mismatch.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_ADDRESS_MISMATCH_H_
+#define NET_QUIC_QUIC_ADDRESS_MISMATCH_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IPEndPoint;
+
+enum QuicAddressMismatch {
+  // The addresses don't match.
+  QUIC_ADDRESS_MISMATCH_BASE = 0,
+  QUIC_ADDRESS_MISMATCH_V4_V4 = 0,
+  QUIC_ADDRESS_MISMATCH_V6_V6 = 1,
+  QUIC_ADDRESS_MISMATCH_V4_V6 = 2,
+  QUIC_ADDRESS_MISMATCH_V6_V4 = 3,
+
+  // The addresses match, but the ports don't match.
+  QUIC_PORT_MISMATCH_BASE = 4,
+  QUIC_PORT_MISMATCH_V4_V4 = 4,
+  QUIC_PORT_MISMATCH_V6_V6 = 5,
+
+  QUIC_ADDRESS_AND_PORT_MATCH_BASE = 6,
+  QUIC_ADDRESS_AND_PORT_MATCH_V4_V4 = 6,
+  QUIC_ADDRESS_AND_PORT_MATCH_V6_V6 = 7,
+
+  QUIC_ADDRESS_MISMATCH_MAX,
+};
+
+// Returns a value of the QuicAddressMismatch enum type that indicates how
+// |first_address| differs from |second_address|. Returns -1 if either address
+// is empty.
+//
+// Only used by the Net.QuicSession.PublicResetAddressMismatch histogram.
+NET_EXPORT_PRIVATE int GetAddressMismatch(const IPEndPoint& first_address,
+                                          const IPEndPoint& second_address);
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_ADDRESS_MISMATCH_H_
diff --git a/net/quic/quic_address_mismatch_test.cc b/net/quic/quic_address_mismatch_test.cc
new file mode 100644
index 0000000..b7c683b
--- /dev/null
+++ b/net/quic/quic_address_mismatch_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_address_mismatch.h"
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+// Test all cases of the GetAddressMismatch function.
+TEST(QuicAddressMismatchTest, GetAddressMismatch) {
+  IPAddressNumber ip4_1;
+  IPAddressNumber ip4_2;
+  IPAddressNumber ip6_1;
+  IPAddressNumber ip6_2;
+  IPAddressNumber ip4_mapped_1;
+  IPAddressNumber ip4_mapped_2;
+  ASSERT_TRUE(ParseIPLiteralToNumber("1.2.3.4", &ip4_1));
+  ASSERT_TRUE(ParseIPLiteralToNumber("5.6.7.8", &ip4_2));
+  ASSERT_TRUE(ParseIPLiteralToNumber("1234::1", &ip6_1));
+  ASSERT_TRUE(ParseIPLiteralToNumber("1234::2", &ip6_2));
+  ip4_mapped_1 = ConvertIPv4NumberToIPv6Number(ip4_1);
+  ip4_mapped_2 = ConvertIPv4NumberToIPv6Number(ip4_2);
+  ASSERT_NE(ip4_1, ip4_2);
+  ASSERT_NE(ip6_1, ip6_2);
+  ASSERT_NE(ip4_mapped_1, ip4_mapped_2);
+
+  EXPECT_EQ(-1, GetAddressMismatch(IPEndPoint(), IPEndPoint()));
+  EXPECT_EQ(-1, GetAddressMismatch(IPEndPoint(), IPEndPoint(ip4_1, 443)));
+  EXPECT_EQ(-1, GetAddressMismatch(IPEndPoint(ip4_1, 443), IPEndPoint()));
+
+  EXPECT_EQ(QUIC_ADDRESS_AND_PORT_MATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 443),
+                               IPEndPoint(ip4_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_AND_PORT_MATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 443),
+                               IPEndPoint(ip4_mapped_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_AND_PORT_MATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 443),
+                               IPEndPoint(ip4_mapped_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_AND_PORT_MATCH_V6_V6,
+            GetAddressMismatch(IPEndPoint(ip6_1, 443),
+                               IPEndPoint(ip6_1, 443)));
+
+  EXPECT_EQ(QUIC_PORT_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 80),
+                               IPEndPoint(ip4_1, 443)));
+  EXPECT_EQ(QUIC_PORT_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 80),
+                               IPEndPoint(ip4_mapped_1, 443)));
+  EXPECT_EQ(QUIC_PORT_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 80),
+                               IPEndPoint(ip4_mapped_1, 443)));
+  EXPECT_EQ(QUIC_PORT_MISMATCH_V6_V6,
+            GetAddressMismatch(IPEndPoint(ip6_1, 80),
+                               IPEndPoint(ip6_1, 443)));
+
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 443),
+                               IPEndPoint(ip4_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 443),
+                               IPEndPoint(ip4_mapped_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 443),
+                               IPEndPoint(ip4_mapped_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 80),
+                               IPEndPoint(ip4_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_1, 80),
+                               IPEndPoint(ip4_mapped_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V4,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 80),
+                               IPEndPoint(ip4_mapped_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V6,
+            GetAddressMismatch(IPEndPoint(ip6_1, 443),
+                               IPEndPoint(ip6_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V6,
+            GetAddressMismatch(IPEndPoint(ip6_1, 80),
+                               IPEndPoint(ip6_2, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V6,
+            GetAddressMismatch(IPEndPoint(ip4_1, 443),
+                               IPEndPoint(ip6_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V6,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 443),
+                               IPEndPoint(ip6_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V6,
+            GetAddressMismatch(IPEndPoint(ip4_1, 80),
+                               IPEndPoint(ip6_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V4_V6,
+            GetAddressMismatch(IPEndPoint(ip4_mapped_1, 80),
+                               IPEndPoint(ip6_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V4,
+            GetAddressMismatch(IPEndPoint(ip6_1, 443),
+                               IPEndPoint(ip4_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V4,
+            GetAddressMismatch(IPEndPoint(ip6_1, 443),
+                               IPEndPoint(ip4_mapped_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V4,
+            GetAddressMismatch(IPEndPoint(ip6_1, 80),
+                               IPEndPoint(ip4_1, 443)));
+  EXPECT_EQ(QUIC_ADDRESS_MISMATCH_V6_V4,
+            GetAddressMismatch(IPEndPoint(ip6_1, 80),
+                               IPEndPoint(ip4_mapped_1, 443)));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_alarm.cc b/net/quic/quic_alarm.cc
new file mode 100644
index 0000000..b5aca8c
--- /dev/null
+++ b/net/quic/quic_alarm.cc
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_alarm.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+QuicAlarm::QuicAlarm(Delegate* delegate)
+    : delegate_(delegate),
+      deadline_(QuicTime::Zero()) {
+}
+
+QuicAlarm::~QuicAlarm() {}
+
+void QuicAlarm::Set(QuicTime deadline) {
+  DCHECK(!IsSet());
+  DCHECK(deadline.IsInitialized());
+  deadline_ = deadline;
+  SetImpl();
+}
+
+void QuicAlarm::Cancel() {
+  deadline_ = QuicTime::Zero();
+  CancelImpl();
+}
+
+void QuicAlarm::Update(QuicTime deadline, QuicTime::Delta granularity) {
+  if (!deadline.IsInitialized()) {
+    Cancel();
+    return;
+  }
+  if (std::abs(deadline.Subtract(deadline_).ToMicroseconds()) <
+          granularity.ToMicroseconds()) {
+    return;
+  }
+  Cancel();
+  Set(deadline);
+}
+
+bool QuicAlarm::IsSet() const {
+  return deadline_.IsInitialized();
+}
+
+void QuicAlarm::Fire() {
+  if (!deadline_.IsInitialized()) {
+    return;
+  }
+
+  deadline_ = QuicTime::Zero();
+  QuicTime deadline = delegate_->OnAlarm();
+  // delegate_->OnAlarm() might call Set(), in which case  deadline_ will
+  // already contain the new value, so don't overwrite it.
+  if (!deadline_.IsInitialized() && deadline.IsInitialized()) {
+    Set(deadline);
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_alarm.h b/net/quic/quic_alarm.h
new file mode 100644
index 0000000..5df4fe0
--- /dev/null
+++ b/net/quic/quic_alarm.h
@@ -0,0 +1,82 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_ALARM_H_
+#define NET_QUIC_QUIC_ALARM_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// Abstract class which represents an alarm which will go off at a
+// scheduled time, and execute the |OnAlarm| method of the delegate.
+// An alarm may be cancelled, in which case it may or may not be
+// removed from the underlying scheduling system, but in either case
+// the task will not be executed.
+class NET_EXPORT_PRIVATE QuicAlarm {
+ public:
+  class NET_EXPORT_PRIVATE Delegate {
+   public:
+    virtual ~Delegate() {}
+
+    // Invoked when the alarm fires.  If the return value is not
+    // infinite, then the alarm will be rescheduled at the
+    // specified time.
+    virtual QuicTime OnAlarm() = 0;
+  };
+
+  explicit QuicAlarm(Delegate* delegate);
+  virtual ~QuicAlarm();
+
+  // Sets the alarm to fire at |deadline|.  Must not be called while
+  // the alarm is set.  To reschedule an alarm, call Cancel() first,
+  // then Set().
+  void Set(QuicTime deadline);
+
+  // Cancels the alarm.  May be called repeatedly.  Does not
+  // guarantee that the underlying scheduling system will remove
+  // the alarm's associated task, but guarantees that the
+  // delegates OnAlarm method will not be called.
+  void Cancel();
+
+  // Cancels and sets the alarm if the |deadline| is farther from the current
+  // deadline than |granularity|, and otherwise does nothing.  If |deadline| is
+  // not initialized, the alarm is cancelled.
+  void Update(QuicTime deadline, QuicTime::Delta granularity);
+
+  bool IsSet() const;
+
+  QuicTime deadline() const { return deadline_; }
+
+ protected:
+  // Subclasses implement this method to perform the platform-specific
+  // scheduling of the alarm.  Is called from Set() or Fire(), after the
+  // deadline has been updated.
+  virtual void SetImpl() = 0;
+
+  // Subclasses implement this method to perform the platform-specific
+  // cancelation of the alarm.
+  virtual void CancelImpl() = 0;
+
+  // Called by subclasses when the alarm fires.  Invokes the
+  // delegates |OnAlarm| if a delegate is set, and if the deadline
+  // has been exceeded.  Implementations which do not remove the
+  // alarm from the underlying scheduler on Cancel() may need to handle
+  // the situation where the task executes before the deadline has been
+  // reached, in which case they need to reschedule the task and must not
+  // call invoke this method.
+  void Fire();
+
+ private:
+  scoped_ptr<Delegate> delegate_;
+  QuicTime deadline_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicAlarm);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_ALARM_H_
diff --git a/net/quic/quic_alarm_test.cc b/net/quic/quic_alarm_test.cc
new file mode 100644
index 0000000..434bb38
--- /dev/null
+++ b/net/quic/quic_alarm_test.cc
@@ -0,0 +1,143 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_alarm.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Return;
+using testing::Invoke;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockDelegate : public QuicAlarm::Delegate {
+ public:
+  MOCK_METHOD0(OnAlarm, QuicTime());
+};
+
+class TestAlarm : public QuicAlarm {
+ public:
+  explicit TestAlarm(QuicAlarm::Delegate* delegate) : QuicAlarm(delegate) {}
+
+  bool scheduled() const { return scheduled_; }
+
+  void FireAlarm() {
+    scheduled_ = false;
+    Fire();
+  }
+
+ protected:
+  virtual void SetImpl() OVERRIDE {
+    DCHECK(deadline().IsInitialized());
+    scheduled_ = true;
+  }
+
+  virtual void CancelImpl() OVERRIDE {
+    DCHECK(!deadline().IsInitialized());
+    scheduled_ = false;
+  }
+
+ private:
+  bool scheduled_;
+};
+
+class QuicAlarmTest : public ::testing::Test {
+ public:
+  QuicAlarmTest()
+      : delegate_(new MockDelegate()),
+        alarm_(delegate_),
+        deadline_(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7))),
+        deadline2_(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(14))),
+        new_deadline_(QuicTime::Zero()) {
+  }
+
+  void ResetAlarm() {
+    alarm_.Set(new_deadline_);
+  }
+
+  MockDelegate* delegate_;  // not owned
+  TestAlarm alarm_;
+  QuicTime deadline_;
+  QuicTime deadline2_;
+  QuicTime new_deadline_;
+};
+
+TEST_F(QuicAlarmTest, IsSet) {
+  EXPECT_FALSE(alarm_.IsSet());
+}
+
+TEST_F(QuicAlarmTest, Set) {
+  QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+  alarm_.Set(deadline);
+  EXPECT_TRUE(alarm_.IsSet());
+  EXPECT_TRUE(alarm_.scheduled());
+  EXPECT_EQ(deadline, alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, Cancel) {
+  QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+  alarm_.Set(deadline);
+  alarm_.Cancel();
+  EXPECT_FALSE(alarm_.IsSet());
+  EXPECT_FALSE(alarm_.scheduled());
+  EXPECT_EQ(QuicTime::Zero(), alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, Update) {
+  QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+  alarm_.Set(deadline);
+  QuicTime new_deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(8));
+  alarm_.Update(new_deadline, QuicTime::Delta::Zero());
+  EXPECT_TRUE(alarm_.IsSet());
+  EXPECT_TRUE(alarm_.scheduled());
+  EXPECT_EQ(new_deadline, alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, UpdateWithZero) {
+  QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+  alarm_.Set(deadline);
+  alarm_.Update(QuicTime::Zero(), QuicTime::Delta::Zero());
+  EXPECT_FALSE(alarm_.IsSet());
+  EXPECT_FALSE(alarm_.scheduled());
+  EXPECT_EQ(QuicTime::Zero(), alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, Fire) {
+  QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+  alarm_.Set(deadline);
+  EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(Return(QuicTime::Zero()));
+  alarm_.FireAlarm();
+  EXPECT_FALSE(alarm_.IsSet());
+  EXPECT_FALSE(alarm_.scheduled());
+  EXPECT_EQ(QuicTime::Zero(), alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, FireAndResetViaReturn) {
+  alarm_.Set(deadline_);
+  EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(Return(deadline2_));
+  alarm_.FireAlarm();
+  EXPECT_TRUE(alarm_.IsSet());
+  EXPECT_TRUE(alarm_.scheduled());
+  EXPECT_EQ(deadline2_, alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, FireAndResetViaSet) {
+  alarm_.Set(deadline_);
+  new_deadline_ = deadline2_;
+  EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(DoAll(
+      Invoke(this, &QuicAlarmTest::ResetAlarm),
+      Return(QuicTime::Zero())));
+  alarm_.FireAlarm();
+  EXPECT_TRUE(alarm_.IsSet());
+  EXPECT_TRUE(alarm_.scheduled());
+  EXPECT_EQ(deadline2_, alarm_.deadline());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_bandwidth.cc b/net/quic/quic_bandwidth.cc
new file mode 100644
index 0000000..cdd2986
--- /dev/null
+++ b/net/quic/quic_bandwidth.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_bandwidth.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace net {
+
+// Highest number that QuicBandwidth can hold.
+const int64 kQuicInfiniteBandwidth = GG_INT64_C(0x7fffffffffffffff);
+
+// static
+QuicBandwidth QuicBandwidth::Zero() {
+  return QuicBandwidth(0);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBitsPerSecond(int64 bits_per_second) {
+  return QuicBandwidth(bits_per_second);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromKBitsPerSecond(int64 k_bits_per_second) {
+  DCHECK(k_bits_per_second < kQuicInfiniteBandwidth / 1000);
+  return QuicBandwidth(k_bits_per_second * 1000);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBytesPerSecond(int64 bytes_per_second) {
+  DCHECK(bytes_per_second < kQuicInfiniteBandwidth / 8);
+  return QuicBandwidth(bytes_per_second * 8);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromKBytesPerSecond(int64 k_bytes_per_second) {
+  DCHECK(k_bytes_per_second < kQuicInfiniteBandwidth / 8000);
+  return QuicBandwidth(k_bytes_per_second * 8000);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBytesAndTimeDelta(QuicByteCount bytes,
+                                                   QuicTime::Delta delta) {
+  DCHECK_LT(bytes,
+            static_cast<uint64>(kQuicInfiniteBandwidth /
+                                (8 * base::Time::kMicrosecondsPerSecond)));
+  int64 bytes_per_second = (bytes * base::Time::kMicrosecondsPerSecond) /
+      delta.ToMicroseconds();
+  return QuicBandwidth(bytes_per_second * 8);
+}
+
+QuicBandwidth::QuicBandwidth(int64 bits_per_second)
+    : bits_per_second_(bits_per_second) {
+  DCHECK_GE(bits_per_second, 0);
+}
+
+int64 QuicBandwidth::ToBitsPerSecond() const {
+  return bits_per_second_;
+}
+
+int64 QuicBandwidth::ToKBitsPerSecond() const {
+  return bits_per_second_ / 1000;
+}
+
+int64 QuicBandwidth::ToBytesPerSecond() const {
+  return bits_per_second_ / 8;
+}
+
+int64 QuicBandwidth::ToKBytesPerSecond() const {
+  return bits_per_second_ / 8000;
+}
+
+QuicByteCount QuicBandwidth::ToBytesPerPeriod(
+    QuicTime::Delta time_period) const {
+  return ToBytesPerSecond() * time_period.ToMicroseconds() /
+      base::Time::kMicrosecondsPerSecond;
+}
+
+int64 QuicBandwidth::ToKBytesPerPeriod(QuicTime::Delta time_period) const {
+  return ToKBytesPerSecond() * time_period.ToMicroseconds() /
+      base::Time::kMicrosecondsPerSecond;
+}
+
+bool QuicBandwidth::IsZero() const {
+  return (bits_per_second_ == 0);
+}
+
+QuicBandwidth QuicBandwidth::Add(const QuicBandwidth& delta) const {
+  return QuicBandwidth(bits_per_second_ + delta.bits_per_second_);
+}
+
+QuicBandwidth QuicBandwidth::Subtract(const QuicBandwidth& delta) const {
+  return QuicBandwidth(bits_per_second_ - delta.bits_per_second_);
+}
+
+QuicBandwidth QuicBandwidth::Scale(float scale_factor) const {
+  return QuicBandwidth(bits_per_second_ * scale_factor);
+}
+
+QuicTime::Delta QuicBandwidth::TransferTime(QuicByteCount bytes) const {
+  return QuicTime::Delta::FromMicroseconds(
+      bytes * 8 * base::Time::kMicrosecondsPerSecond / bits_per_second_);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_bandwidth.h b/net/quic/quic_bandwidth.h
new file mode 100644
index 0000000..4ae953d
--- /dev/null
+++ b/net/quic/quic_bandwidth.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// QuicBandwidth represents a bandwidth, stored in bits per second resolution.
+
+#ifndef NET_QUIC_QUIC_BANDWIDTH_H_
+#define NET_QUIC_QUIC_BANDWIDTH_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+typedef uint64 QuicByteCount;
+
+class NET_EXPORT_PRIVATE QuicBandwidth {
+ public:
+  // Creates a new QuicBandwidth with an internal value of 0.
+  static QuicBandwidth Zero();
+
+  // Create a new QuicBandwidth holding the bits per second.
+  static QuicBandwidth FromBitsPerSecond(int64 bits_per_second);
+
+  // Create a new QuicBandwidth holding the kilo bits per second.
+  static QuicBandwidth FromKBitsPerSecond(int64 k_bits_per_second);
+
+  // Create a new QuicBandwidth holding the bytes per second.
+  static QuicBandwidth FromBytesPerSecond(int64 bytes_per_second);
+
+  // Create a new QuicBandwidth holding the kilo bytes per second.
+  static QuicBandwidth FromKBytesPerSecond(int64 k_bytes_per_second);
+
+  // Create a new QuicBandwidth based on the bytes per the elapsed delta.
+  static QuicBandwidth FromBytesAndTimeDelta(QuicByteCount bytes,
+                                             QuicTime::Delta delta);
+
+  int64 ToBitsPerSecond() const;
+
+  int64 ToKBitsPerSecond() const;
+
+  int64 ToBytesPerSecond() const;
+
+  int64 ToKBytesPerSecond() const;
+
+  QuicByteCount ToBytesPerPeriod(QuicTime::Delta time_period) const;
+
+  int64 ToKBytesPerPeriod(QuicTime::Delta time_period) const;
+
+  bool IsZero() const;
+
+  QuicBandwidth Add(const QuicBandwidth& delta) const;
+
+  QuicBandwidth Subtract(const QuicBandwidth& delta) const;
+
+  QuicBandwidth Scale(float scale_factor) const;
+
+  QuicTime::Delta TransferTime(QuicByteCount bytes) const;
+
+ private:
+  explicit QuicBandwidth(int64 bits_per_second);
+  int64 bits_per_second_;
+};
+
+// Non-member relational operators for QuicBandwidth.
+inline bool operator==(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return lhs.ToBitsPerSecond() == rhs.ToBitsPerSecond();
+}
+inline bool operator!=(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return !(lhs == rhs);
+}
+inline bool operator<(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return lhs.ToBitsPerSecond() < rhs.ToBitsPerSecond();
+}
+inline bool operator>(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return rhs < lhs;
+}
+inline bool operator<=(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return !(rhs < lhs);
+}
+inline bool operator>=(QuicBandwidth lhs, QuicBandwidth rhs) {
+  return !(lhs < rhs);
+}
+
+}  // namespace net
+#endif  // NET_QUIC_QUIC_BANDWIDTH_H_
diff --git a/net/quic/quic_bandwidth_test.cc b/net/quic/quic_bandwidth_test.cc
new file mode 100644
index 0000000..e9913db
--- /dev/null
+++ b/net/quic/quic_bandwidth_test.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_bandwidth.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicBandwidthTest : public ::testing::Test {
+};
+
+TEST_F(QuicBandwidthTest, FromTo) {
+  EXPECT_EQ(QuicBandwidth::FromKBitsPerSecond(1),
+            QuicBandwidth::FromBitsPerSecond(1000));
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1),
+            QuicBandwidth::FromBytesPerSecond(1000));
+  EXPECT_EQ(QuicBandwidth::FromBitsPerSecond(8000),
+            QuicBandwidth::FromBytesPerSecond(1000));
+  EXPECT_EQ(QuicBandwidth::FromKBitsPerSecond(8),
+            QuicBandwidth::FromKBytesPerSecond(1));
+
+  EXPECT_EQ(0, QuicBandwidth::Zero().ToBitsPerSecond());
+  EXPECT_EQ(0, QuicBandwidth::Zero().ToKBitsPerSecond());
+  EXPECT_EQ(0, QuicBandwidth::Zero().ToBytesPerSecond());
+  EXPECT_EQ(0, QuicBandwidth::Zero().ToKBytesPerSecond());
+
+  EXPECT_EQ(1, QuicBandwidth::FromBitsPerSecond(1000).ToKBitsPerSecond());
+  EXPECT_EQ(1000, QuicBandwidth::FromKBitsPerSecond(1).ToBitsPerSecond());
+  EXPECT_EQ(1, QuicBandwidth::FromBytesPerSecond(1000).ToKBytesPerSecond());
+  EXPECT_EQ(1000, QuicBandwidth::FromKBytesPerSecond(1).ToBytesPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, Add) {
+  QuicBandwidth bandwidht_1 = QuicBandwidth::FromKBitsPerSecond(1);
+  QuicBandwidth bandwidht_2 = QuicBandwidth::FromKBytesPerSecond(1);
+
+  EXPECT_EQ(9000, bandwidht_1.Add(bandwidht_2).ToBitsPerSecond());
+  EXPECT_EQ(9000, bandwidht_2.Add(bandwidht_1).ToBitsPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, Subtract) {
+  QuicBandwidth bandwidht_1 = QuicBandwidth::FromKBitsPerSecond(1);
+  QuicBandwidth bandwidht_2 = QuicBandwidth::FromKBytesPerSecond(1);
+
+  EXPECT_EQ(7000, bandwidht_2.Subtract(bandwidht_1).ToBitsPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, TimeDelta) {
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1000),
+            QuicBandwidth::FromBytesAndTimeDelta(
+                1000, QuicTime::Delta::FromMilliseconds(1)));
+
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(10),
+            QuicBandwidth::FromBytesAndTimeDelta(
+                1000, QuicTime::Delta::FromMilliseconds(100)));
+}
+
+TEST_F(QuicBandwidthTest, Scale) {
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(500),
+            QuicBandwidth::FromKBytesPerSecond(1000).Scale(0.5f));
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(750),
+            QuicBandwidth::FromKBytesPerSecond(1000).Scale(0.75f));
+  EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1250),
+            QuicBandwidth::FromKBytesPerSecond(1000).Scale(1.25f));
+}
+
+
+TEST_F(QuicBandwidthTest, BytesPerPeriod) {
+  EXPECT_EQ(2000u, QuicBandwidth::FromKBytesPerSecond(2000).ToBytesPerPeriod(
+      QuicTime::Delta::FromMilliseconds(1)));
+  EXPECT_EQ(2u, QuicBandwidth::FromKBytesPerSecond(2000).ToKBytesPerPeriod(
+      QuicTime::Delta::FromMilliseconds(1)));
+  EXPECT_EQ(200000u, QuicBandwidth::FromKBytesPerSecond(2000).ToBytesPerPeriod(
+      QuicTime::Delta::FromMilliseconds(100)));
+  EXPECT_EQ(200u, QuicBandwidth::FromKBytesPerSecond(2000).ToKBytesPerPeriod(
+      QuicTime::Delta::FromMilliseconds(100)));
+}
+
+TEST_F(QuicBandwidthTest, TransferTime) {
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+            QuicBandwidth::FromKBytesPerSecond(1).TransferTime(1000));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_blocked_writer_interface.h b/net/quic/quic_blocked_writer_interface.h
new file mode 100644
index 0000000..1c3a6fe
--- /dev/null
+++ b/net/quic/quic_blocked_writer_interface.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is an interface for all objects that want to be notified that
+// the underlying UDP socket is available for writing (not write blocked
+// anymore).
+
+#ifndef NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
+#define NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicBlockedWriterInterface {
+ public:
+  virtual ~QuicBlockedWriterInterface() {}
+
+  // Called by the PacketWriter when the underlying socket becomes writable
+  // so that the BlockedWriter can go ahead and try writing.
+  virtual void OnCanWrite() = 0;
+};
+
+}  // namespace net
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+// Hash pointers as if they were int's, but bring more entropy to the lower
+// bits.
+template <>
+struct hash<net::QuicBlockedWriterInterface*> {
+  std::size_t operator()(const net::QuicBlockedWriterInterface* ptr) const {
+    size_t k = reinterpret_cast<size_t>(ptr);
+    return k + (k >> 6);
+  }
+};
+}
+#endif
+
+#endif  // NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
diff --git a/net/quic/quic_client_session.cc b/net/quic/quic_client_session.cc
new file mode 100644
index 0000000..5454c10
--- /dev/null
+++ b/net/quic/quic_client_session.cc
@@ -0,0 +1,872 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_client_session.h"
+
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/crypto/quic_server_info.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_server_id.h"
+#include "net/quic/quic_stream_factory.h"
+#include "net/spdy/spdy_session.h"
+#include "net/ssl/channel_id_service.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+
+// The length of time to wait for a 0-RTT handshake to complete
+// before allowing the requests to possibly proceed over TCP.
+const int k0RttHandshakeTimeoutMs = 300;
+
+// Histograms for tracking down the crashes from http://crbug.com/354669
+// Note: these values must be kept in sync with the corresponding values in:
+// tools/metrics/histograms/histograms.xml
+enum Location {
+  DESTRUCTOR = 0,
+  ADD_OBSERVER = 1,
+  TRY_CREATE_STREAM = 2,
+  CREATE_OUTGOING_RELIABLE_STREAM = 3,
+  NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER = 4,
+  NOTIFY_FACTORY_OF_SESSION_CLOSED = 5,
+  NUM_LOCATIONS = 6,
+};
+
+void RecordUnexpectedOpenStreams(Location location) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedOpenStreams", location,
+                            NUM_LOCATIONS);
+}
+
+void RecordUnexpectedObservers(Location location) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedObservers", location,
+                            NUM_LOCATIONS);
+}
+
+void RecordUnexpectedNotGoingAway(Location location) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.UnexpectedNotGoingAway", location,
+                            NUM_LOCATIONS);
+}
+
+// Histogram for recording the different reasons that a QUIC session is unable
+// to complete the handshake.
+enum HandshakeFailureReason {
+  HANDSHAKE_FAILURE_UNKNOWN = 0,
+  HANDSHAKE_FAILURE_BLACK_HOLE = 1,
+  HANDSHAKE_FAILURE_PUBLIC_RESET = 2,
+  NUM_HANDSHAKE_FAILURE_REASONS = 3,
+};
+
+void RecordHandshakeFailureReason(HandshakeFailureReason reason) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "Net.QuicSession.ConnectionClose.HandshakeNotConfirmed.Reason",
+      reason, NUM_HANDSHAKE_FAILURE_REASONS);
+}
+
+// Note: these values must be kept in sync with the corresponding values in:
+// tools/metrics/histograms/histograms.xml
+enum HandshakeState {
+  STATE_STARTED = 0,
+  STATE_ENCRYPTION_ESTABLISHED = 1,
+  STATE_HANDSHAKE_CONFIRMED = 2,
+  STATE_FAILED = 3,
+  NUM_HANDSHAKE_STATES = 4
+};
+
+void RecordHandshakeState(HandshakeState state) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicHandshakeState", state,
+                            NUM_HANDSHAKE_STATES);
+}
+
+}  // namespace
+
+QuicClientSession::StreamRequest::StreamRequest() : stream_(nullptr) {}
+
+QuicClientSession::StreamRequest::~StreamRequest() {
+  CancelRequest();
+}
+
+int QuicClientSession::StreamRequest::StartRequest(
+    const base::WeakPtr<QuicClientSession>& session,
+    QuicReliableClientStream** stream,
+    const CompletionCallback& callback) {
+  session_ = session;
+  stream_ = stream;
+  int rv = session_->TryCreateStream(this, stream_);
+  if (rv == ERR_IO_PENDING) {
+    callback_ = callback;
+  }
+
+  return rv;
+}
+
+void QuicClientSession::StreamRequest::CancelRequest() {
+  if (session_)
+    session_->CancelRequest(this);
+  session_.reset();
+  callback_.Reset();
+}
+
+void QuicClientSession::StreamRequest::OnRequestCompleteSuccess(
+    QuicReliableClientStream* stream) {
+  session_.reset();
+  *stream_ = stream;
+  ResetAndReturn(&callback_).Run(OK);
+}
+
+void QuicClientSession::StreamRequest::OnRequestCompleteFailure(int rv) {
+  session_.reset();
+  ResetAndReturn(&callback_).Run(rv);
+}
+
+QuicClientSession::QuicClientSession(
+    QuicConnection* connection,
+    scoped_ptr<DatagramClientSocket> socket,
+    QuicStreamFactory* stream_factory,
+    TransportSecurityState* transport_security_state,
+    scoped_ptr<QuicServerInfo> server_info,
+    const QuicConfig& config,
+    base::TaskRunner* task_runner,
+    NetLog* net_log)
+    : QuicClientSessionBase(connection, config),
+      require_confirmation_(false),
+      stream_factory_(stream_factory),
+      socket_(socket.Pass()),
+      read_buffer_(new IOBufferWithSize(kMaxPacketSize)),
+      transport_security_state_(transport_security_state),
+      server_info_(server_info.Pass()),
+      read_pending_(false),
+      num_total_streams_(0),
+      task_runner_(task_runner),
+      net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_QUIC_SESSION)),
+      logger_(new QuicConnectionLogger(net_log_)),
+      num_packets_read_(0),
+      going_away_(false),
+      weak_factory_(this) {
+  connection->set_debug_visitor(logger_);
+}
+
+void QuicClientSession::InitializeSession(
+    const QuicServerId& server_id,
+    QuicCryptoClientConfig* crypto_config,
+    QuicCryptoClientStreamFactory* crypto_client_stream_factory) {
+  server_host_port_ = server_id.host_port_pair();
+  crypto_stream_.reset(
+      crypto_client_stream_factory ?
+          crypto_client_stream_factory->CreateQuicCryptoClientStream(
+              server_id, this, crypto_config) :
+          new QuicCryptoClientStream(server_id, this,
+                                     new ProofVerifyContextChromium(net_log_),
+                                     crypto_config));
+  QuicClientSessionBase::InitializeSession();
+  // TODO(rch): pass in full host port proxy pair
+  net_log_.BeginEvent(
+      NetLog::TYPE_QUIC_SESSION,
+      NetLog::StringCallback("host", &server_id.host()));
+}
+
+QuicClientSession::~QuicClientSession() {
+  if (!streams()->empty())
+    RecordUnexpectedOpenStreams(DESTRUCTOR);
+  if (!observers_.empty())
+    RecordUnexpectedObservers(DESTRUCTOR);
+  if (!going_away_)
+    RecordUnexpectedNotGoingAway(DESTRUCTOR);
+
+  while (!streams()->empty() ||
+         !observers_.empty() ||
+         !stream_requests_.empty()) {
+    // The session must be closed before it is destroyed.
+    DCHECK(streams()->empty());
+    CloseAllStreams(ERR_UNEXPECTED);
+    DCHECK(observers_.empty());
+    CloseAllObservers(ERR_UNEXPECTED);
+
+    connection()->set_debug_visitor(nullptr);
+    net_log_.EndEvent(NetLog::TYPE_QUIC_SESSION);
+
+    while (!stream_requests_.empty()) {
+      StreamRequest* request = stream_requests_.front();
+      stream_requests_.pop_front();
+      request->OnRequestCompleteFailure(ERR_ABORTED);
+    }
+  }
+
+  if (connection()->connected()) {
+    // Ensure that the connection is closed by the time the session is
+    // destroyed.
+    connection()->CloseConnection(QUIC_INTERNAL_ERROR, false);
+  }
+
+  if (IsEncryptionEstablished())
+    RecordHandshakeState(STATE_ENCRYPTION_ESTABLISHED);
+  if (IsCryptoHandshakeConfirmed())
+    RecordHandshakeState(STATE_HANDSHAKE_CONFIRMED);
+  else
+    RecordHandshakeState(STATE_FAILED);
+
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.NumTotalStreams", num_total_streams_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicNumSentClientHellos",
+                       crypto_stream_->num_sent_client_hellos());
+  if (!IsCryptoHandshakeConfirmed())
+    return;
+
+  // Sending one client_hello means we had zero handshake-round-trips.
+  int round_trip_handshakes = crypto_stream_->num_sent_client_hellos() - 1;
+
+  // Don't bother with these histogram during tests, which mock out
+  // num_sent_client_hellos().
+  if (round_trip_handshakes < 0 || !stream_factory_)
+    return;
+
+  bool port_selected = stream_factory_->enable_port_selection();
+  SSLInfo ssl_info;
+  if (!GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
+    if (port_selected) {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectSelectPortForHTTP",
+                                  round_trip_handshakes, 0, 3, 4);
+    } else {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectRandomPortForHTTP",
+                                  round_trip_handshakes, 0, 3, 4);
+      if (require_confirmation_) {
+        UMA_HISTOGRAM_CUSTOM_COUNTS(
+            "Net.QuicSession.ConnectRandomPortRequiringConfirmationForHTTP",
+            round_trip_handshakes, 0, 3, 4);
+      }
+    }
+  } else {
+    if (port_selected) {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectSelectPortForHTTPS",
+                                  round_trip_handshakes, 0, 3, 4);
+    } else {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.ConnectRandomPortForHTTPS",
+                                  round_trip_handshakes, 0, 3, 4);
+      if (require_confirmation_) {
+        UMA_HISTOGRAM_CUSTOM_COUNTS(
+            "Net.QuicSession.ConnectRandomPortRequiringConfirmationForHTTPS",
+            round_trip_handshakes, 0, 3, 4);
+      }
+    }
+  }
+  const QuicConnectionStats stats = connection()->GetStats();
+  if (stats.max_sequence_reordering == 0)
+    return;
+  const uint64 kMaxReordering = 100;
+  uint64 reordering = kMaxReordering;
+  if (stats.min_rtt_us > 0 ) {
+    reordering =
+        GG_UINT64_C(100) * stats.max_time_reordering_us / stats.min_rtt_us;
+  }
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTime",
+                                reordering, 0, kMaxReordering, 50);
+  if (stats.min_rtt_us > 100 * 1000) {
+    UMA_HISTOGRAM_CUSTOM_COUNTS("Net.QuicSession.MaxReorderingTimeLongRtt",
+                                reordering, 0, kMaxReordering, 50);
+  }
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.MaxReordering",
+                       stats.max_sequence_reordering);
+}
+
+void QuicClientSession::OnStreamFrames(
+    const std::vector<QuicStreamFrame>& frames) {
+  // Record total number of stream frames.
+  UMA_HISTOGRAM_COUNTS("Net.QuicNumStreamFramesInPacket", frames.size());
+
+  // Record number of frames per stream in packet.
+  typedef std::map<QuicStreamId, size_t> FrameCounter;
+  FrameCounter frames_per_stream;
+  for (size_t i = 0; i < frames.size(); ++i) {
+    frames_per_stream[frames[i].stream_id]++;
+  }
+  for (FrameCounter::const_iterator it = frames_per_stream.begin();
+       it != frames_per_stream.end(); ++it) {
+    UMA_HISTOGRAM_COUNTS("Net.QuicNumStreamFramesPerStreamInPacket",
+                         it->second);
+  }
+
+  return QuicSession::OnStreamFrames(frames);
+}
+
+void QuicClientSession::AddObserver(Observer* observer) {
+  if (going_away_) {
+    RecordUnexpectedObservers(ADD_OBSERVER);
+    observer->OnSessionClosed(ERR_UNEXPECTED);
+    return;
+  }
+
+  DCHECK(!ContainsKey(observers_, observer));
+  observers_.insert(observer);
+}
+
+void QuicClientSession::RemoveObserver(Observer* observer) {
+  DCHECK(ContainsKey(observers_, observer));
+  observers_.erase(observer);
+}
+
+int QuicClientSession::TryCreateStream(StreamRequest* request,
+                                       QuicReliableClientStream** stream) {
+  if (!crypto_stream_->encryption_established()) {
+    DLOG(DFATAL) << "Encryption not established.";
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  if (goaway_received()) {
+    DVLOG(1) << "Going away.";
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  if (!connection()->connected()) {
+    DVLOG(1) << "Already closed.";
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  if (going_away_) {
+    RecordUnexpectedOpenStreams(TRY_CREATE_STREAM);
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  if (GetNumOpenStreams() < get_max_open_streams()) {
+    *stream = CreateOutgoingReliableStreamImpl();
+    return OK;
+  }
+
+  stream_requests_.push_back(request);
+  return ERR_IO_PENDING;
+}
+
+void QuicClientSession::CancelRequest(StreamRequest* request) {
+  // Remove |request| from the queue while preserving the order of the
+  // other elements.
+  StreamRequestQueue::iterator it =
+      std::find(stream_requests_.begin(), stream_requests_.end(), request);
+  if (it != stream_requests_.end()) {
+    it = stream_requests_.erase(it);
+  }
+}
+
+QuicReliableClientStream* QuicClientSession::CreateOutgoingDataStream() {
+  if (!crypto_stream_->encryption_established()) {
+    DVLOG(1) << "Encryption not active so no outgoing stream created.";
+    return nullptr;
+  }
+  if (GetNumOpenStreams() >= get_max_open_streams()) {
+    DVLOG(1) << "Failed to create a new outgoing stream. "
+             << "Already " << GetNumOpenStreams() << " open.";
+    return nullptr;
+  }
+  if (goaway_received()) {
+    DVLOG(1) << "Failed to create a new outgoing stream. "
+             << "Already received goaway.";
+    return nullptr;
+  }
+  if (going_away_) {
+    RecordUnexpectedOpenStreams(CREATE_OUTGOING_RELIABLE_STREAM);
+    return nullptr;
+  }
+  return CreateOutgoingReliableStreamImpl();
+}
+
+QuicReliableClientStream*
+QuicClientSession::CreateOutgoingReliableStreamImpl() {
+  DCHECK(connection()->connected());
+  QuicReliableClientStream* stream =
+      new QuicReliableClientStream(GetNextStreamId(), this, net_log_);
+  ActivateStream(stream);
+  ++num_total_streams_;
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.NumOpenStreams", GetNumOpenStreams());
+  return stream;
+}
+
+QuicCryptoClientStream* QuicClientSession::GetCryptoStream() {
+  return crypto_stream_.get();
+};
+
+// TODO(rtenneti): Add unittests for GetSSLInfo which exercise the various ways
+// we learn about SSL info (sync vs async vs cached).
+bool QuicClientSession::GetSSLInfo(SSLInfo* ssl_info) const {
+  ssl_info->Reset();
+  if (!cert_verify_result_) {
+    return false;
+  }
+
+  ssl_info->cert_status = cert_verify_result_->cert_status;
+  ssl_info->cert = cert_verify_result_->verified_cert;
+
+  // TODO(wtc): Define QUIC "cipher suites".
+  // Report the TLS cipher suite that most closely resembles the crypto
+  // parameters of the QUIC connection.
+  QuicTag aead = crypto_stream_->crypto_negotiated_params().aead;
+  int cipher_suite;
+  int security_bits;
+  switch (aead) {
+    case kAESG:
+      cipher_suite = 0xc02f;  // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+      security_bits = 128;
+      break;
+    case kCC12:
+      cipher_suite = 0xcc13;  // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
+      security_bits = 256;
+      break;
+    default:
+      NOTREACHED();
+      return false;
+  }
+  int ssl_connection_status = 0;
+  ssl_connection_status |=
+      (cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) <<
+       SSL_CONNECTION_CIPHERSUITE_SHIFT;
+  ssl_connection_status |=
+      (SSL_CONNECTION_VERSION_QUIC & SSL_CONNECTION_VERSION_MASK) <<
+       SSL_CONNECTION_VERSION_SHIFT;
+
+  ssl_info->public_key_hashes = cert_verify_result_->public_key_hashes;
+  ssl_info->is_issued_by_known_root =
+      cert_verify_result_->is_issued_by_known_root;
+
+  ssl_info->connection_status = ssl_connection_status;
+  ssl_info->client_cert_sent = false;
+  ssl_info->channel_id_sent = crypto_stream_->WasChannelIDSent();
+  ssl_info->security_bits = security_bits;
+  ssl_info->handshake_type = SSLInfo::HANDSHAKE_FULL;
+  ssl_info->pinning_failure_log = pinning_failure_log_;
+  return true;
+}
+
+int QuicClientSession::CryptoConnect(bool require_confirmation,
+                                     const CompletionCallback& callback) {
+  require_confirmation_ = require_confirmation;
+  handshake_start_ = base::TimeTicks::Now();
+  RecordHandshakeState(STATE_STARTED);
+  DCHECK(flow_controller());
+  if (!crypto_stream_->CryptoConnect()) {
+    // TODO(wtc): change crypto_stream_.CryptoConnect() to return a
+    // QuicErrorCode and map it to a net error code.
+    return ERR_CONNECTION_FAILED;
+  }
+
+  if (IsCryptoHandshakeConfirmed())
+    return OK;
+
+  // Unless we require handshake confirmation, activate the session if
+  // we have established initial encryption.
+  if (!require_confirmation_ && IsEncryptionEstablished()) {
+    // To mitigate the effects of hanging 0-RTT connections, set up a timer to
+    // cancel any requests, if the handshake takes too long.
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&QuicClientSession::OnConnectTimeout,
+                   weak_factory_.GetWeakPtr()),
+        base::TimeDelta::FromMilliseconds(k0RttHandshakeTimeoutMs));
+    return OK;
+
+  }
+
+  callback_ = callback;
+  return ERR_IO_PENDING;
+}
+
+int QuicClientSession::ResumeCryptoConnect(const CompletionCallback& callback) {
+
+  if (IsCryptoHandshakeConfirmed())
+    return OK;
+
+  if (!connection()->connected())
+    return ERR_QUIC_HANDSHAKE_FAILED;
+
+  callback_ = callback;
+  return ERR_IO_PENDING;
+}
+
+int QuicClientSession::GetNumSentClientHellos() const {
+  return crypto_stream_->num_sent_client_hellos();
+}
+
+bool QuicClientSession::CanPool(const std::string& hostname) const {
+  DCHECK(connection()->connected());
+  SSLInfo ssl_info;
+  if (!GetSSLInfo(&ssl_info) || !ssl_info.cert.get()) {
+    // We can always pool with insecure QUIC sessions.
+    return true;
+  }
+
+  return SpdySession::CanPool(transport_security_state_, ssl_info,
+                              server_host_port_.host(), hostname);
+}
+
+QuicDataStream* QuicClientSession::CreateIncomingDataStream(
+    QuicStreamId id) {
+  DLOG(ERROR) << "Server push not supported";
+  return nullptr;
+}
+
+void QuicClientSession::CloseStream(QuicStreamId stream_id) {
+  ReliableQuicStream* stream = GetStream(stream_id);
+  if (stream) {
+    logger_->UpdateReceivedFrameCounts(
+        stream_id, stream->num_frames_received(),
+        stream->num_duplicate_frames_received());
+  }
+  QuicSession::CloseStream(stream_id);
+  OnClosedStream();
+}
+
+void QuicClientSession::SendRstStream(QuicStreamId id,
+                                      QuicRstStreamErrorCode error,
+                                      QuicStreamOffset bytes_written) {
+  QuicSession::SendRstStream(id, error, bytes_written);
+  OnClosedStream();
+}
+
+void QuicClientSession::OnClosedStream() {
+  if (GetNumOpenStreams() < get_max_open_streams() &&
+      !stream_requests_.empty() &&
+      crypto_stream_->encryption_established() &&
+      !goaway_received() &&
+      !going_away_ &&
+      connection()->connected()) {
+    StreamRequest* request = stream_requests_.front();
+    stream_requests_.pop_front();
+    request->OnRequestCompleteSuccess(CreateOutgoingReliableStreamImpl());
+  }
+
+  if (GetNumOpenStreams() == 0) {
+    stream_factory_->OnIdleSession(this);
+  }
+}
+
+void QuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  if (!callback_.is_null() &&
+      (!require_confirmation_ || event == HANDSHAKE_CONFIRMED)) {
+    // TODO(rtenneti): Currently for all CryptoHandshakeEvent events, callback_
+    // could be called because there are no error events in CryptoHandshakeEvent
+    // enum. If error events are added to CryptoHandshakeEvent, then the
+    // following code needs to changed.
+    base::ResetAndReturn(&callback_).Run(OK);
+  }
+  if (event == HANDSHAKE_CONFIRMED) {
+    UMA_HISTOGRAM_TIMES("Net.QuicSession.HandshakeConfirmedTime",
+                        base::TimeTicks::Now() - handshake_start_);
+    ObserverSet::iterator it = observers_.begin();
+    while (it != observers_.end()) {
+      Observer* observer = *it;
+      ++it;
+      observer->OnCryptoHandshakeConfirmed();
+    }
+  }
+  QuicSession::OnCryptoHandshakeEvent(event);
+}
+
+void QuicClientSession::OnCryptoHandshakeMessageSent(
+    const CryptoHandshakeMessage& message) {
+  logger_->OnCryptoHandshakeMessageSent(message);
+}
+
+void QuicClientSession::OnCryptoHandshakeMessageReceived(
+    const CryptoHandshakeMessage& message) {
+  logger_->OnCryptoHandshakeMessageReceived(message);
+}
+
+void QuicClientSession::OnConnectionClosed(QuicErrorCode error,
+                                           bool from_peer) {
+  DCHECK(!connection()->connected());
+  logger_->OnConnectionClosed(error, from_peer);
+  if (from_peer) {
+    UMA_HISTOGRAM_SPARSE_SLOWLY(
+        "Net.QuicSession.ConnectionCloseErrorCodeServer", error);
+  } else {
+    UMA_HISTOGRAM_SPARSE_SLOWLY(
+        "Net.QuicSession.ConnectionCloseErrorCodeClient", error);
+  }
+
+  if (error == QUIC_CONNECTION_TIMED_OUT) {
+    UMA_HISTOGRAM_COUNTS(
+        "Net.QuicSession.ConnectionClose.NumOpenStreams.TimedOut",
+        GetNumOpenStreams());
+    if (IsCryptoHandshakeConfirmed()) {
+      if (GetNumOpenStreams() > 0) {
+        UMA_HISTOGRAM_BOOLEAN(
+            "Net.QuicSession.TimedOutWithOpenStreams.HasUnackedPackets",
+            connection()->sent_packet_manager().HasUnackedPackets());
+        UMA_HISTOGRAM_COUNTS(
+            "Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveRTOCount",
+            connection()->sent_packet_manager().consecutive_rto_count());
+        UMA_HISTOGRAM_COUNTS(
+            "Net.QuicSession.TimedOutWithOpenStreams.ConsecutiveTLPCount",
+            connection()->sent_packet_manager().consecutive_tlp_count());
+      }
+    } else {
+      UMA_HISTOGRAM_COUNTS(
+          "Net.QuicSession.ConnectionClose.NumOpenStreams.HandshakeTimedOut",
+          GetNumOpenStreams());
+      UMA_HISTOGRAM_COUNTS(
+          "Net.QuicSession.ConnectionClose.NumTotalStreams.HandshakeTimedOut",
+          num_total_streams_);
+    }
+  }
+
+  if (!IsCryptoHandshakeConfirmed()) {
+    if (error == QUIC_PUBLIC_RESET) {
+      RecordHandshakeFailureReason(HANDSHAKE_FAILURE_PUBLIC_RESET);
+    } else if (connection()->GetStats().packets_received == 0) {
+      RecordHandshakeFailureReason(HANDSHAKE_FAILURE_BLACK_HOLE);
+      UMA_HISTOGRAM_SPARSE_SLOWLY(
+          "Net.QuicSession.ConnectionClose.HandshakeFailureBlackHole.QuicError",
+          error);
+    } else {
+      RecordHandshakeFailureReason(HANDSHAKE_FAILURE_UNKNOWN);
+      UMA_HISTOGRAM_SPARSE_SLOWLY(
+          "Net.QuicSession.ConnectionClose.HandshakeFailureUnknown.QuicError",
+          error);
+    }
+  }
+
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.QuicVersion",
+                              connection()->version());
+  NotifyFactoryOfSessionGoingAway();
+  if (!callback_.is_null()) {
+    base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
+  }
+  socket_->Close();
+  QuicSession::OnConnectionClosed(error, from_peer);
+  DCHECK(streams()->empty());
+  CloseAllStreams(ERR_UNEXPECTED);
+  CloseAllObservers(ERR_UNEXPECTED);
+  NotifyFactoryOfSessionClosedLater();
+}
+
+void QuicClientSession::OnSuccessfulVersionNegotiation(
+    const QuicVersion& version) {
+  logger_->OnSuccessfulVersionNegotiation(version);
+  QuicSession::OnSuccessfulVersionNegotiation(version);
+}
+
+void QuicClientSession::OnProofValid(
+    const QuicCryptoClientConfig::CachedState& cached) {
+  DCHECK(cached.proof_valid());
+
+  if (!server_info_ || !server_info_->IsReadyToPersist()) {
+    return;
+  }
+
+  QuicServerInfo::State* state = server_info_->mutable_state();
+
+  state->server_config = cached.server_config();
+  state->source_address_token = cached.source_address_token();
+  state->server_config_sig = cached.signature();
+  state->certs = cached.certs();
+
+  server_info_->Persist();
+}
+
+void QuicClientSession::OnProofVerifyDetailsAvailable(
+    const ProofVerifyDetails& verify_details) {
+  const ProofVerifyDetailsChromium* verify_details_chromium =
+      reinterpret_cast<const ProofVerifyDetailsChromium*>(&verify_details);
+  CertVerifyResult* result_copy = new CertVerifyResult;
+  result_copy->CopyFrom(verify_details_chromium->cert_verify_result);
+  cert_verify_result_.reset(result_copy);
+  pinning_failure_log_ = verify_details_chromium->pinning_failure_log;
+  logger_->OnCertificateVerified(*cert_verify_result_);
+}
+
+void QuicClientSession::StartReading() {
+  if (read_pending_) {
+    return;
+  }
+  read_pending_ = true;
+  int rv = socket_->Read(read_buffer_.get(),
+                         read_buffer_->size(),
+                         base::Bind(&QuicClientSession::OnReadComplete,
+                                    weak_factory_.GetWeakPtr()));
+  if (rv == ERR_IO_PENDING) {
+    num_packets_read_ = 0;
+    return;
+  }
+
+  if (++num_packets_read_ > 32) {
+    num_packets_read_ = 0;
+    // Data was read, process it.
+    // Schedule the work through the message loop to 1) prevent infinite
+    // recursion and 2) avoid blocking the thread for too long.
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&QuicClientSession::OnReadComplete,
+                   weak_factory_.GetWeakPtr(), rv));
+  } else {
+    OnReadComplete(rv);
+  }
+}
+
+void QuicClientSession::CloseSessionOnError(int error) {
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.CloseSessionOnError", -error);
+  CloseSessionOnErrorInner(error, QUIC_INTERNAL_ERROR);
+  NotifyFactoryOfSessionClosed();
+}
+
+void QuicClientSession::CloseSessionOnErrorInner(int net_error,
+                                                 QuicErrorCode quic_error) {
+  if (!callback_.is_null()) {
+    base::ResetAndReturn(&callback_).Run(net_error);
+  }
+  CloseAllStreams(net_error);
+  CloseAllObservers(net_error);
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CLOSE_ON_ERROR,
+      NetLog::IntegerCallback("net_error", net_error));
+
+  if (connection()->connected())
+    connection()->CloseConnection(quic_error, false);
+  DCHECK(!connection()->connected());
+}
+
+void QuicClientSession::CloseAllStreams(int net_error) {
+  while (!streams()->empty()) {
+    ReliableQuicStream* stream = streams()->begin()->second;
+    QuicStreamId id = stream->id();
+    static_cast<QuicReliableClientStream*>(stream)->OnError(net_error);
+    CloseStream(id);
+  }
+}
+
+void QuicClientSession::CloseAllObservers(int net_error) {
+  while (!observers_.empty()) {
+    Observer* observer = *observers_.begin();
+    observers_.erase(observer);
+    observer->OnSessionClosed(net_error);
+  }
+}
+
+base::Value* QuicClientSession::GetInfoAsValue(
+    const std::set<HostPortPair>& aliases) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("version", QuicVersionToString(connection()->version()));
+  dict->SetInteger("open_streams", GetNumOpenStreams());
+  base::ListValue* stream_list = new base::ListValue();
+  for (base::hash_map<QuicStreamId, QuicDataStream*>::const_iterator it
+           = streams()->begin();
+       it != streams()->end();
+       ++it) {
+    stream_list->Append(new base::StringValue(
+        base::Uint64ToString(it->second->id())));
+  }
+  dict->Set("active_streams", stream_list);
+
+  dict->SetInteger("total_streams", num_total_streams_);
+  dict->SetString("peer_address", peer_address().ToString());
+  dict->SetString("connection_id", base::Uint64ToString(connection_id()));
+  dict->SetBoolean("connected", connection()->connected());
+  const QuicConnectionStats& stats = connection()->GetStats();
+  dict->SetInteger("packets_sent", stats.packets_sent);
+  dict->SetInteger("packets_received", stats.packets_received);
+  dict->SetInteger("packets_lost", stats.packets_lost);
+  SSLInfo ssl_info;
+  dict->SetBoolean("secure", GetSSLInfo(&ssl_info) && ssl_info.cert.get());
+
+  base::ListValue* alias_list = new base::ListValue();
+  for (std::set<HostPortPair>::const_iterator it = aliases.begin();
+       it != aliases.end(); it++) {
+    alias_list->Append(new base::StringValue(it->ToString()));
+  }
+  dict->Set("aliases", alias_list);
+
+  return dict;
+}
+
+base::WeakPtr<QuicClientSession> QuicClientSession::GetWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+void QuicClientSession::OnReadComplete(int result) {
+  read_pending_ = false;
+  if (result == 0)
+    result = ERR_CONNECTION_CLOSED;
+
+  if (result < 0) {
+    DVLOG(1) << "Closing session on read error: " << result;
+    UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ReadError", -result);
+    NotifyFactoryOfSessionGoingAway();
+    CloseSessionOnErrorInner(result, QUIC_PACKET_READ_ERROR);
+    NotifyFactoryOfSessionClosedLater();
+    return;
+  }
+
+  QuicEncryptedPacket packet(read_buffer_->data(), result);
+  IPEndPoint local_address;
+  IPEndPoint peer_address;
+  socket_->GetLocalAddress(&local_address);
+  socket_->GetPeerAddress(&peer_address);
+  // ProcessUdpPacket might result in |this| being deleted, so we
+  // use a weak pointer to be safe.
+  connection()->ProcessUdpPacket(local_address, peer_address, packet);
+  if (!connection()->connected()) {
+    NotifyFactoryOfSessionClosedLater();
+    return;
+  }
+  StartReading();
+}
+
+void QuicClientSession::NotifyFactoryOfSessionGoingAway() {
+  going_away_ = true;
+  if (stream_factory_)
+    stream_factory_->OnSessionGoingAway(this);
+}
+
+void QuicClientSession::NotifyFactoryOfSessionClosedLater() {
+  if (!streams()->empty())
+    RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);
+
+  if (!going_away_)
+    RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED_LATER);
+
+  going_away_ = true;
+  DCHECK_EQ(0u, GetNumOpenStreams());
+  DCHECK(!connection()->connected());
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&QuicClientSession::NotifyFactoryOfSessionClosed,
+                 weak_factory_.GetWeakPtr()));
+}
+
+void QuicClientSession::NotifyFactoryOfSessionClosed() {
+  if (!streams()->empty())
+    RecordUnexpectedOpenStreams(NOTIFY_FACTORY_OF_SESSION_CLOSED);
+
+  if (!going_away_)
+    RecordUnexpectedNotGoingAway(NOTIFY_FACTORY_OF_SESSION_CLOSED);
+
+  going_away_ = true;
+  DCHECK_EQ(0u, GetNumOpenStreams());
+  // Will delete |this|.
+  if (stream_factory_)
+    stream_factory_->OnSessionClosed(this);
+}
+
+void QuicClientSession::OnConnectTimeout() {
+  DCHECK(callback_.is_null());
+  DCHECK(IsEncryptionEstablished());
+
+  if (IsCryptoHandshakeConfirmed())
+    return;
+
+  // TODO(rch): re-enable this code once beta is cut.
+  //  if (stream_factory_)
+  //    stream_factory_->OnSessionConnectTimeout(this);
+  //  CloseAllStreams(ERR_QUIC_HANDSHAKE_FAILED);
+  //  DCHECK_EQ(0u, GetNumOpenStreams());
+}
+
+}  // namespace net
diff --git a/net/quic/quic_client_session.h b/net/quic/quic_client_session.h
new file mode 100644
index 0000000..fa712c2
--- /dev/null
+++ b/net/quic/quic_client_session.h
@@ -0,0 +1,256 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A client specific QuicSession subclass.  This class owns the underlying
+// QuicConnection and QuicConnectionHelper objects.  The connection stores
+// a non-owning pointer to the helper so this session needs to ensure that
+// the helper outlives the connection.
+
+#ifndef NET_QUIC_QUIC_CLIENT_SESSION_H_
+#define NET_QUIC_QUIC_CLIENT_SESSION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_server.h"
+#include "net/quic/quic_client_session_base.h"
+#include "net/quic/quic_connection_logger.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+class CertVerifyResult;
+class DatagramClientSocket;
+class QuicConnectionHelper;
+class QuicCryptoClientStreamFactory;
+class QuicServerId;
+class QuicServerInfo;
+class QuicStreamFactory;
+class SSLInfo;
+class TransportSecurityState;
+
+namespace test {
+class QuicClientSessionPeer;
+}  // namespace test
+
+class NET_EXPORT_PRIVATE QuicClientSession : public QuicClientSessionBase {
+ public:
+  // An interface for observing events on a session.
+  class NET_EXPORT_PRIVATE Observer {
+   public:
+    virtual ~Observer() {}
+    virtual void OnCryptoHandshakeConfirmed() = 0;
+    virtual void OnSessionClosed(int error) = 0;
+  };
+
+  // A helper class used to manage a request to create a stream.
+  class NET_EXPORT_PRIVATE StreamRequest {
+   public:
+    StreamRequest();
+    ~StreamRequest();
+
+    // Starts a request to create a stream.  If OK is returned, then
+    // |stream| will be updated with the newly created stream.  If
+    // ERR_IO_PENDING is returned, then when the request is eventuallly
+    // complete |callback| will be called.
+    int StartRequest(const base::WeakPtr<QuicClientSession>& session,
+                     QuicReliableClientStream** stream,
+                     const CompletionCallback& callback);
+
+    // Cancels any pending stream creation request. May be called
+    // repeatedly.
+    void CancelRequest();
+
+   private:
+    friend class QuicClientSession;
+
+    // Called by |session_| for an asynchronous request when the stream
+    // request has finished successfully.
+    void OnRequestCompleteSuccess(QuicReliableClientStream* stream);
+
+    // Called by |session_| for an asynchronous request when the stream
+    // request has finished with an error. Also called with ERR_ABORTED
+    // if |session_| is destroyed while the stream request is still pending.
+    void OnRequestCompleteFailure(int rv);
+
+    base::WeakPtr<QuicClientSession> session_;
+    CompletionCallback callback_;
+    QuicReliableClientStream** stream_;
+
+    DISALLOW_COPY_AND_ASSIGN(StreamRequest);
+  };
+
+  // Constructs a new session which will own |connection|, but not
+  // |stream_factory|, which must outlive this session.
+  // TODO(rch): decouple the factory from the session via a Delegate interface.
+  QuicClientSession(QuicConnection* connection,
+                    scoped_ptr<DatagramClientSocket> socket,
+                    QuicStreamFactory* stream_factory,
+                    TransportSecurityState* transport_security_state,
+                    scoped_ptr<QuicServerInfo> server_info,
+                    const QuicConfig& config,
+                    base::TaskRunner* task_runner,
+                    NetLog* net_log);
+  virtual ~QuicClientSession();
+
+  // Initialize session's connection to |server_id|.
+  void InitializeSession(
+      const QuicServerId& server_id,
+      QuicCryptoClientConfig* config,
+      QuicCryptoClientStreamFactory* crypto_client_stream_factory);
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  // Attempts to create a new stream.  If the stream can be
+  // created immediately, returns OK.  If the open stream limit
+  // has been reached, returns ERR_IO_PENDING, and |request|
+  // will be added to the stream requets queue and will
+  // be completed asynchronously.
+  // TODO(rch): remove |stream| from this and use setter on |request|
+  // and fix in spdy too.
+  int TryCreateStream(StreamRequest* request,
+                      QuicReliableClientStream** stream);
+
+  // Cancels the pending stream creation request.
+  void CancelRequest(StreamRequest* request);
+
+  // QuicSession methods:
+  virtual void OnStreamFrames(
+      const std::vector<QuicStreamFrame>& frames) OVERRIDE;
+  virtual QuicReliableClientStream* CreateOutgoingDataStream() OVERRIDE;
+  virtual QuicCryptoClientStream* GetCryptoStream() OVERRIDE;
+  virtual void CloseStream(QuicStreamId stream_id) OVERRIDE;
+  virtual void SendRstStream(QuicStreamId id,
+                             QuicRstStreamErrorCode error,
+                             QuicStreamOffset bytes_written) OVERRIDE;
+  virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) OVERRIDE;
+  virtual void OnCryptoHandshakeMessageSent(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+  virtual void OnCryptoHandshakeMessageReceived(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+  virtual bool GetSSLInfo(SSLInfo* ssl_info) const OVERRIDE;
+
+  // QuicClientSessionBase methods:
+  virtual void OnProofValid(
+      const QuicCryptoClientConfig::CachedState& cached) OVERRIDE;
+  virtual void OnProofVerifyDetailsAvailable(
+      const ProofVerifyDetails& verify_details) OVERRIDE;
+
+  // QuicConnectionVisitorInterface methods:
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) OVERRIDE;
+  virtual void OnSuccessfulVersionNegotiation(
+      const QuicVersion& version) OVERRIDE;
+
+  // Performs a crypto handshake with the server.
+  int CryptoConnect(bool require_confirmation,
+                    const CompletionCallback& callback);
+
+  // Resumes a crypto handshake with the server after a timeout.
+  int ResumeCryptoConnect(const CompletionCallback& callback);
+
+  // Causes the QuicConnectionHelper to start reading from the socket
+  // and passing the data along to the QuicConnection.
+  void StartReading();
+
+  // Close the session because of |error| and notifies the factory
+  // that this session has been closed, which will delete the session.
+  void CloseSessionOnError(int error);
+
+  base::Value* GetInfoAsValue(const std::set<HostPortPair>& aliases);
+
+  const BoundNetLog& net_log() const { return net_log_; }
+
+  base::WeakPtr<QuicClientSession> GetWeakPtr();
+
+  // Returns the number of client hello messages that have been sent on the
+  // crypto stream. If the handshake has completed then this is one greater
+  // than the number of round-trips needed for the handshake.
+  int GetNumSentClientHellos() const;
+
+  // Returns true if |hostname| may be pooled onto this session.  If this
+  // is a secure QUIC session, then |hostname| must match the certificate
+  // presented during the handshake.
+  bool CanPool(const std::string& hostname) const;
+
+ protected:
+  // QuicSession methods:
+  virtual QuicDataStream* CreateIncomingDataStream(QuicStreamId id) OVERRIDE;
+
+ private:
+  friend class test::QuicClientSessionPeer;
+
+  typedef std::set<Observer*> ObserverSet;
+  typedef std::list<StreamRequest*> StreamRequestQueue;
+
+  QuicReliableClientStream* CreateOutgoingReliableStreamImpl();
+  // A completion callback invoked when a read completes.
+  void OnReadComplete(int result);
+
+  void OnClosedStream();
+
+  // A Session may be closed via any of three methods:
+  // OnConnectionClosed - called by the connection when the connection has been
+  //     closed, perhaps due to a timeout or a protocol error.
+  // CloseSessionOnError - called from the owner of the session,
+  //     the QuicStreamFactory, when there is an error.
+  // OnReadComplete - when there is a read error.
+  // This method closes all stream and performs any necessary cleanup.
+  void CloseSessionOnErrorInner(int net_error, QuicErrorCode quic_error);
+
+  void CloseAllStreams(int net_error);
+  void CloseAllObservers(int net_error);
+
+  // Notifies the factory that this session is going away and no more streams
+  // should be created from it.  This needs to be called before closing any
+  // streams, because closing a stream may cause a new stream to be created.
+  void NotifyFactoryOfSessionGoingAway();
+
+  // Posts a task to notify the factory that this session has been closed.
+  void NotifyFactoryOfSessionClosedLater();
+
+  // Notifies the factory that this session has been closed which will
+  // delete |this|.
+  void NotifyFactoryOfSessionClosed();
+
+  void OnConnectTimeout();
+
+  HostPortPair server_host_port_;
+  bool require_confirmation_;
+  scoped_ptr<QuicCryptoClientStream> crypto_stream_;
+  QuicStreamFactory* stream_factory_;
+  scoped_ptr<DatagramClientSocket> socket_;
+  scoped_refptr<IOBufferWithSize> read_buffer_;
+  TransportSecurityState* transport_security_state_;
+  scoped_ptr<QuicServerInfo> server_info_;
+  scoped_ptr<CertVerifyResult> cert_verify_result_;
+  std::string pinning_failure_log_;
+  ObserverSet observers_;
+  StreamRequestQueue stream_requests_;
+  bool read_pending_;
+  CompletionCallback callback_;
+  size_t num_total_streams_;
+  base::TaskRunner* task_runner_;
+  BoundNetLog net_log_;
+  base::TimeTicks handshake_start_;  // Time the handshake was started.
+  QuicConnectionLogger* logger_;  // Owned by |connection_|.
+  // Number of packets read in the current read loop.
+  size_t num_packets_read_;
+  // True when the session is going away, and streams may no longer be created
+  // on this session. Existing stream will continue to be processed.
+  bool going_away_;
+  base::WeakPtrFactory<QuicClientSession> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicClientSession);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CLIENT_SESSION_H_
diff --git a/net/quic/quic_client_session_base.cc b/net/quic/quic_client_session_base.cc
new file mode 100644
index 0000000..40d4b86
--- /dev/null
+++ b/net/quic/quic_client_session_base.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_client_session_base.h"
+
+#include "net/quic/quic_flags.h"
+
+namespace net {
+
+QuicClientSessionBase::QuicClientSessionBase(
+    QuicConnection* connection,
+    const QuicConfig& config)
+    : QuicSession(connection, config) {}
+
+QuicClientSessionBase::~QuicClientSessionBase() {}
+
+void QuicClientSessionBase::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  QuicSession::OnCryptoHandshakeEvent(event);
+  // Set FEC policy for streams immediately after sending CHLO and before any
+  // more data is sent.
+  if (!FLAGS_enable_quic_fec ||
+      event != ENCRYPTION_FIRST_ESTABLISHED ||
+      !config()->HasSendConnectionOptions() ||
+      !ContainsQuicTag(config()->SendConnectionOptions(), kFHDR)) {
+    return;
+  }
+  // kFHDR config maps to FEC protection always for headers stream.
+  // TODO(jri): Add crypto stream in addition to headers for kHDR.
+  headers_stream_->set_fec_policy(FEC_PROTECT_ALWAYS);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_client_session_base.h b/net/quic/quic_client_session_base.h
new file mode 100644
index 0000000..834d006
--- /dev/null
+++ b/net/quic/quic_client_session_base.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CLIENT_SESSION_BASE_H_
+#define NET_QUIC_QUIC_CLIENT_SESSION_BASE_H_
+
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+// Base class for all client-specific QuicSession subclasses.
+class NET_EXPORT_PRIVATE QuicClientSessionBase : public QuicSession {
+ public:
+  QuicClientSessionBase(QuicConnection* connection,
+                        const QuicConfig& config);
+
+  virtual ~QuicClientSessionBase();
+
+  // Called when the proof in |cached| is marked valid.  If this is a secure
+  // QUIC session, then this will happen only after the proof verifier
+  // completes.  If this is an insecure QUIC connection, this will happen
+  // as soon as a valid config is discovered (either from the cache or
+  // from the server).
+  virtual void OnProofValid(
+      const QuicCryptoClientConfig::CachedState& cached) = 0;
+
+  // Called when proof verification details become available, either because
+  // proof verification is complete, or when cached details are used. This
+  // will only be called for secure QUIC connections.
+  virtual void OnProofVerifyDetailsAvailable(
+      const ProofVerifyDetails& verify_details) = 0;
+
+  // Override base class to set FEC policy before any data is sent by client.
+  virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicClientSessionBase);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CLIENT_SESSION_BASE_H_
diff --git a/net/quic/quic_client_session_test.cc b/net/quic/quic_client_session_test.cc
new file mode 100644
index 0000000..9758ed4
--- /dev/null
+++ b/net/quic/quic_client_session_test.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_client_session.h"
+
+#include <vector>
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/rand_util.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_server_info.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_client_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_test_utils.h"
+#include "net/test/cert_test_util.h"
+#include "net/udp/datagram_client_socket.h"
+
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "www.example.org";
+const uint16 kServerPort = 80;
+
+class QuicClientSessionTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+  QuicClientSessionTest()
+      : connection_(
+            new PacketSavingConnection(false, SupportedVersions(GetParam()))),
+        session_(connection_, GetSocket().Pass(), nullptr,
+                 &transport_security_state_,
+                 make_scoped_ptr((QuicServerInfo*)nullptr), DefaultQuicConfig(),
+                 base::MessageLoop::current()->message_loop_proxy().get(),
+                 &net_log_) {
+    session_.InitializeSession(QuicServerId(kServerHostname, kServerPort, false,
+                                            PRIVACY_MODE_DISABLED),
+                                &crypto_config_, nullptr);
+    session_.config()->SetDefaults();
+    crypto_config_.SetDefaults();
+  }
+
+  virtual void TearDown() OVERRIDE {
+    session_.CloseSessionOnError(ERR_ABORTED);
+  }
+
+  scoped_ptr<DatagramClientSocket> GetSocket() {
+    socket_factory_.AddSocketDataProvider(&socket_data_);
+    return socket_factory_.CreateDatagramClientSocket(
+        DatagramSocket::DEFAULT_BIND, base::Bind(&base::RandInt),
+        &net_log_, NetLog::Source());
+  }
+
+  void CompleteCryptoHandshake() {
+    ASSERT_EQ(ERR_IO_PENDING,
+              session_.CryptoConnect(false, callback_.callback()));
+    CryptoTestUtils::HandshakeWithFakeServer(
+        connection_, session_.GetCryptoStream());
+    ASSERT_EQ(OK, callback_.WaitForResult());
+  }
+
+  PacketSavingConnection* connection_;
+  CapturingNetLog net_log_;
+  MockClientSocketFactory socket_factory_;
+  StaticSocketDataProvider socket_data_;
+  TransportSecurityState transport_security_state_;
+  QuicClientSession session_;
+  MockClock clock_;
+  MockRandom random_;
+  QuicConnectionVisitorInterface* visitor_;
+  TestCompletionCallback callback_;
+  QuicCryptoClientConfig crypto_config_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicClientSessionTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicClientSessionTest, CryptoConnect) {
+  CompleteCryptoHandshake();
+}
+
+TEST_P(QuicClientSessionTest, MaxNumStreams) {
+  CompleteCryptoHandshake();
+
+  std::vector<QuicReliableClientStream*> streams;
+  for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+    QuicReliableClientStream* stream = session_.CreateOutgoingDataStream();
+    EXPECT_TRUE(stream);
+    streams.push_back(stream);
+  }
+  EXPECT_FALSE(session_.CreateOutgoingDataStream());
+
+  // Close a stream and ensure I can now open a new one.
+  session_.CloseStream(streams[0]->id());
+  EXPECT_TRUE(session_.CreateOutgoingDataStream());
+}
+
+TEST_P(QuicClientSessionTest, MaxNumStreamsViaRequest) {
+  CompleteCryptoHandshake();
+
+  std::vector<QuicReliableClientStream*> streams;
+  for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+    QuicReliableClientStream* stream = session_.CreateOutgoingDataStream();
+    EXPECT_TRUE(stream);
+    streams.push_back(stream);
+  }
+
+  QuicReliableClientStream* stream;
+  QuicClientSession::StreamRequest stream_request;
+  TestCompletionCallback callback;
+  ASSERT_EQ(ERR_IO_PENDING,
+            stream_request.StartRequest(session_.GetWeakPtr(), &stream,
+                                        callback.callback()));
+
+  // Close a stream and ensure I can now open a new one.
+  session_.CloseStream(streams[0]->id());
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_EQ(OK, callback.WaitForResult());
+  EXPECT_TRUE(stream != nullptr);
+}
+
+TEST_P(QuicClientSessionTest, GoAwayReceived) {
+  CompleteCryptoHandshake();
+
+  // After receiving a GoAway, I should no longer be able to create outgoing
+  // streams.
+  session_.OnGoAway(QuicGoAwayFrame(QUIC_PEER_GOING_AWAY, 1u, "Going away."));
+  EXPECT_EQ(nullptr, session_.CreateOutgoingDataStream());
+}
+
+TEST_P(QuicClientSessionTest, CanPool) {
+  // Load a cert that is valid for:
+  //   www.example.org
+  //   mail.example.org
+  //   www.example.com
+
+  ProofVerifyDetailsChromium details;
+  details.cert_verify_result.verified_cert =
+      ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
+  ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
+
+  session_.OnProofVerifyDetailsAvailable(details);
+  CompleteCryptoHandshake();
+
+
+  EXPECT_TRUE(session_.CanPool("www.example.org"));
+  EXPECT_TRUE(session_.CanPool("mail.example.org"));
+  EXPECT_TRUE(session_.CanPool("mail.example.com"));
+  EXPECT_FALSE(session_.CanPool("mail.google.com"));
+}
+
+TEST_P(QuicClientSessionTest, ConnectionPooledWithTlsChannelId) {
+  // Load a cert that is valid for:
+  //   www.example.org
+  //   mail.example.org
+  //   www.example.com
+
+  ProofVerifyDetailsChromium details;
+  details.cert_verify_result.verified_cert =
+      ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
+  ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
+
+  session_.OnProofVerifyDetailsAvailable(details);
+  CompleteCryptoHandshake();
+  QuicClientSessionPeer::SetChannelIDSent(&session_, true);
+
+  EXPECT_TRUE(session_.CanPool("www.example.org"));
+  EXPECT_TRUE(session_.CanPool("mail.example.org"));
+  EXPECT_FALSE(session_.CanPool("mail.example.com"));
+  EXPECT_FALSE(session_.CanPool("mail.google.com"));
+}
+
+TEST_P(QuicClientSessionTest, ConnectionNotPooledWithDifferentPin) {
+  uint8 primary_pin = 1;
+  uint8 backup_pin = 2;
+  uint8 bad_pin = 3;
+  AddPin(&transport_security_state_, "mail.example.org", primary_pin,
+         backup_pin);
+
+  ProofVerifyDetailsChromium details;
+  details.cert_verify_result.verified_cert =
+      ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
+  details.cert_verify_result.is_issued_by_known_root = true;
+  details.cert_verify_result.public_key_hashes.push_back(
+      GetTestHashValue(bad_pin));
+
+  ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
+
+  session_.OnProofVerifyDetailsAvailable(details);
+  CompleteCryptoHandshake();
+  QuicClientSessionPeer::SetChannelIDSent(&session_, true);
+
+  EXPECT_FALSE(session_.CanPool("mail.example.org"));
+}
+
+TEST_P(QuicClientSessionTest, ConnectionPooledWithMatchingPin) {
+  uint8 primary_pin = 1;
+  uint8 backup_pin = 2;
+  AddPin(&transport_security_state_, "mail.example.org", primary_pin,
+         backup_pin);
+
+  ProofVerifyDetailsChromium details;
+  details.cert_verify_result.verified_cert =
+      ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
+  details.cert_verify_result.is_issued_by_known_root = true;
+  details.cert_verify_result.public_key_hashes.push_back(
+      GetTestHashValue(primary_pin));
+
+  ASSERT_TRUE(details.cert_verify_result.verified_cert.get());
+
+  session_.OnProofVerifyDetailsAvailable(details);
+  CompleteCryptoHandshake();
+  QuicClientSessionPeer::SetChannelIDSent(&session_, true);
+
+  EXPECT_TRUE(session_.CanPool("mail.example.org"));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_clock.cc b/net/quic/quic_clock.cc
new file mode 100644
index 0000000..9d5ac6d
--- /dev/null
+++ b/net/quic/quic_clock.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_clock.h"
+
+#include "base/time/time.h"
+
+namespace net {
+
+QuicClock::QuicClock() {
+}
+
+QuicClock::~QuicClock() {}
+
+QuicTime QuicClock::ApproximateNow() const {
+  // At the moment, Chrome does not have a distinct notion of ApproximateNow().
+  // We should consider implementing this using MessageLoop::recent_time_.
+  return Now();
+}
+
+QuicTime QuicClock::Now() const {
+  return QuicTime(base::TimeTicks::Now());
+}
+
+QuicWallTime QuicClock::WallNow() const {
+  return QuicWallTime::FromUNIXSeconds(base::Time::Now().ToTimeT());
+}
+
+}  // namespace net
diff --git a/net/quic/quic_clock.h b/net/quic/quic_clock.h
new file mode 100644
index 0000000..4e96d4c
--- /dev/null
+++ b/net/quic/quic_clock.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CLOCK_H_
+#define NET_QUIC_QUIC_CLOCK_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+typedef double WallTime;
+
+// Clock to efficiently retrieve an approximately accurate time from an
+// EpollServer.
+class NET_EXPORT_PRIVATE QuicClock {
+ public:
+  QuicClock();
+  virtual ~QuicClock();
+
+  // Returns the approximate current time as a QuicTime object.
+  virtual QuicTime ApproximateNow() const;
+
+  // Returns the current time as a QuicTime object.
+  // Note: this use significant resources please use only if needed.
+  virtual QuicTime Now() const;
+
+  // WallNow returns the current wall-time - a time that is consistent across
+  // different clocks.
+  virtual QuicWallTime WallNow() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicClock);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CLOCK_H_
diff --git a/net/quic/quic_clock_test.cc b/net/quic/quic_clock_test.cc
new file mode 100644
index 0000000..6ee4540
--- /dev/null
+++ b/net/quic/quic_clock_test.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_clock.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicClockTest, Now) {
+  QuicClock clock;
+
+  QuicTime start(base::TimeTicks::Now());
+  QuicTime now = clock.ApproximateNow();
+  QuicTime end(base::TimeTicks::Now());
+
+  EXPECT_LE(start, now);
+  EXPECT_LE(now, end);
+}
+
+TEST(QuicClockTest, WallNow) {
+  QuicClock clock;
+
+  base::Time start = base::Time::Now();
+  QuicWallTime now = clock.WallNow();
+  base::Time end = base::Time::Now();
+
+  // If end > start, then we can check now is between start and end.
+  if (end > start) {
+    EXPECT_LE(static_cast<uint64>(start.ToTimeT()), now.ToUNIXSeconds());
+    EXPECT_LE(now.ToUNIXSeconds(), static_cast<uint64>(end.ToTimeT()));
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_config.cc b/net/quic/quic_config.cc
new file mode 100644
index 0000000..5330b7c
--- /dev/null
+++ b/net/quic/quic_config.cc
@@ -0,0 +1,720 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_config.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_utils.h"
+
+using std::min;
+using std::string;
+
+namespace net {
+
+// Reads the value corresponding to |name_| from |msg| into |out|. If the
+// |name_| is absent in |msg| and |presence| is set to OPTIONAL |out| is set
+// to |default_value|.
+QuicErrorCode ReadUint32(const CryptoHandshakeMessage& msg,
+                         QuicTag tag,
+                         QuicConfigPresence presence,
+                         uint32 default_value,
+                         uint32* out,
+                         string* error_details) {
+  DCHECK(error_details != nullptr);
+  QuicErrorCode error = msg.GetUint32(tag, out);
+  switch (error) {
+    case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+      if (presence == PRESENCE_REQUIRED) {
+        *error_details = "Missing " + QuicUtils::TagToString(tag);
+        break;
+      }
+      error = QUIC_NO_ERROR;
+      *out = default_value;
+      break;
+    case QUIC_NO_ERROR:
+      break;
+    default:
+      *error_details = "Bad " + QuicUtils::TagToString(tag);
+      break;
+  }
+  return error;
+}
+
+
+QuicConfigValue::QuicConfigValue(QuicTag tag,
+                                 QuicConfigPresence presence)
+    : tag_(tag),
+      presence_(presence) {
+}
+QuicConfigValue::~QuicConfigValue() {}
+
+QuicNegotiableValue::QuicNegotiableValue(QuicTag tag,
+                                         QuicConfigPresence presence)
+    : QuicConfigValue(tag, presence),
+      negotiated_(false) {
+}
+QuicNegotiableValue::~QuicNegotiableValue() {}
+
+QuicNegotiableUint32::QuicNegotiableUint32(QuicTag tag,
+                                           QuicConfigPresence presence)
+    : QuicNegotiableValue(tag, presence),
+      max_value_(0),
+      default_value_(0) {
+}
+QuicNegotiableUint32::~QuicNegotiableUint32() {}
+
+void QuicNegotiableUint32::set(uint32 max, uint32 default_value) {
+  DCHECK_LE(default_value, max);
+  max_value_ = max;
+  default_value_ = default_value;
+}
+
+uint32 QuicNegotiableUint32::GetUint32() const {
+  if (negotiated_) {
+    return negotiated_value_;
+  }
+  return default_value_;
+}
+
+void QuicNegotiableUint32::ToHandshakeMessage(
+    CryptoHandshakeMessage* out) const {
+  if (negotiated_) {
+    out->SetValue(tag_, negotiated_value_);
+  } else {
+    out->SetValue(tag_, max_value_);
+  }
+}
+
+QuicErrorCode QuicNegotiableUint32::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(!negotiated_);
+  DCHECK(error_details != nullptr);
+  uint32 value;
+  QuicErrorCode error = ReadUint32(peer_hello,
+                                   tag_,
+                                   presence_,
+                                   default_value_,
+                                   &value,
+                                   error_details);
+  if (error != QUIC_NO_ERROR) {
+    return error;
+  }
+  if (hello_type == SERVER && value > max_value_) {
+    *error_details =
+        "Invalid value received for " + QuicUtils::TagToString(tag_);
+    return QUIC_INVALID_NEGOTIATED_VALUE;
+  }
+
+  negotiated_ = true;
+  negotiated_value_ = min(value, max_value_);
+  return QUIC_NO_ERROR;
+}
+
+QuicNegotiableTag::QuicNegotiableTag(QuicTag tag, QuicConfigPresence presence)
+    : QuicNegotiableValue(tag, presence),
+      negotiated_tag_(0),
+      default_value_(0) {
+}
+
+QuicNegotiableTag::~QuicNegotiableTag() {}
+
+void QuicNegotiableTag::set(const QuicTagVector& possible,
+                            QuicTag default_value) {
+  DCHECK(ContainsQuicTag(possible, default_value));
+  possible_values_ = possible;
+  default_value_ = default_value;
+}
+
+QuicTag QuicNegotiableTag::GetTag() const {
+  if (negotiated_) {
+    return negotiated_tag_;
+  }
+  return default_value_;
+}
+
+void QuicNegotiableTag::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+  if (negotiated_) {
+    // Because of the way we serialize and parse handshake messages we can
+    // serialize this as value and still parse it as a vector.
+    out->SetValue(tag_, negotiated_tag_);
+  } else {
+    out->SetVector(tag_, possible_values_);
+  }
+}
+
+QuicErrorCode QuicNegotiableTag::ReadVector(
+    const CryptoHandshakeMessage& msg,
+    const QuicTag** out,
+    size_t* out_length,
+    string* error_details) const {
+  DCHECK(error_details != nullptr);
+  QuicErrorCode error = msg.GetTaglist(tag_, out, out_length);
+  switch (error) {
+    case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+      if (presence_ == PRESENCE_REQUIRED) {
+        *error_details = "Missing " + QuicUtils::TagToString(tag_);
+        break;
+      }
+      error = QUIC_NO_ERROR;
+      *out_length = 1;
+      *out = &default_value_;
+
+    case QUIC_NO_ERROR:
+      break;
+    default:
+      *error_details = "Bad " + QuicUtils::TagToString(tag_);
+      break;
+  }
+  return error;
+}
+
+QuicErrorCode QuicNegotiableTag::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(!negotiated_);
+  DCHECK(error_details != nullptr);
+  const QuicTag* received_tags;
+  size_t received_tags_length;
+  QuicErrorCode error = ReadVector(peer_hello, &received_tags,
+                                   &received_tags_length, error_details);
+  if (error != QUIC_NO_ERROR) {
+    return error;
+  }
+
+  if (hello_type == SERVER) {
+    if (received_tags_length != 1 ||
+        !ContainsQuicTag(possible_values_,  *received_tags)) {
+      *error_details = "Invalid " + QuicUtils::TagToString(tag_);
+      return QUIC_INVALID_NEGOTIATED_VALUE;
+    }
+    negotiated_tag_ = *received_tags;
+  } else {
+    QuicTag negotiated_tag;
+    if (!QuicUtils::FindMutualTag(possible_values_,
+                                  received_tags,
+                                  received_tags_length,
+                                  QuicUtils::LOCAL_PRIORITY,
+                                  &negotiated_tag,
+                                  nullptr)) {
+      *error_details = "Unsupported " + QuicUtils::TagToString(tag_);
+      return QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP;
+    }
+    negotiated_tag_ = negotiated_tag;
+  }
+
+  negotiated_ = true;
+  return QUIC_NO_ERROR;
+}
+
+QuicFixedUint32::QuicFixedUint32(QuicTag tag, QuicConfigPresence presence)
+    : QuicConfigValue(tag, presence),
+      has_send_value_(false),
+      has_receive_value_(false) {
+}
+QuicFixedUint32::~QuicFixedUint32() {}
+
+bool QuicFixedUint32::HasSendValue() const {
+  return has_send_value_;
+}
+
+uint32 QuicFixedUint32::GetSendValue() const {
+  LOG_IF(DFATAL, !has_send_value_)
+      << "No send value to get for tag:" << QuicUtils::TagToString(tag_);
+  return send_value_;
+}
+
+void QuicFixedUint32::SetSendValue(uint32 value) {
+  has_send_value_ = true;
+  send_value_ = value;
+}
+
+bool QuicFixedUint32::HasReceivedValue() const {
+  return has_receive_value_;
+}
+
+uint32 QuicFixedUint32::GetReceivedValue() const {
+  LOG_IF(DFATAL, !has_receive_value_)
+      << "No receive value to get for tag:" << QuicUtils::TagToString(tag_);
+  return receive_value_;
+}
+
+void QuicFixedUint32::SetReceivedValue(uint32 value) {
+  has_receive_value_ = true;
+  receive_value_ = value;
+}
+
+void QuicFixedUint32::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+  if (has_send_value_) {
+    out->SetValue(tag_, send_value_);
+  }
+}
+
+QuicErrorCode QuicFixedUint32::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+  QuicErrorCode error = peer_hello.GetUint32(tag_, &receive_value_);
+  switch (error) {
+    case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+      if (presence_ == PRESENCE_OPTIONAL) {
+        return QUIC_NO_ERROR;
+      }
+      *error_details = "Missing " + QuicUtils::TagToString(tag_);
+      break;
+    case QUIC_NO_ERROR:
+      has_receive_value_ = true;
+      break;
+    default:
+      *error_details = "Bad " + QuicUtils::TagToString(tag_);
+      break;
+  }
+  return error;
+}
+
+QuicFixedTag::QuicFixedTag(QuicTag name,
+                           QuicConfigPresence presence)
+    : QuicConfigValue(name, presence),
+      has_send_value_(false),
+      has_receive_value_(false) {
+}
+
+QuicFixedTag::~QuicFixedTag() {}
+
+bool QuicFixedTag::HasSendValue() const {
+  return has_send_value_;
+}
+
+uint32 QuicFixedTag::GetSendValue() const {
+  LOG_IF(DFATAL, !has_send_value_)
+      << "No send value to get for tag:" << QuicUtils::TagToString(tag_);
+  return send_value_;
+}
+
+void QuicFixedTag::SetSendValue(uint32 value) {
+  has_send_value_ = true;
+  send_value_ = value;
+}
+
+bool QuicFixedTag::HasReceivedValue() const {
+  return has_receive_value_;
+}
+
+uint32 QuicFixedTag::GetReceivedValue() const {
+  LOG_IF(DFATAL, !has_receive_value_)
+      << "No receive value to get for tag:" << QuicUtils::TagToString(tag_);
+  return receive_value_;
+}
+
+void QuicFixedTag::SetReceivedValue(uint32 value) {
+  has_receive_value_ = true;
+  receive_value_ = value;
+}
+
+void QuicFixedTag::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+  if (has_send_value_) {
+    out->SetValue(tag_, send_value_);
+  }
+}
+
+QuicErrorCode QuicFixedTag::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+  QuicErrorCode error = peer_hello.GetUint32(tag_, &receive_value_);
+  switch (error) {
+    case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+      if (presence_ == PRESENCE_OPTIONAL) {
+        return QUIC_NO_ERROR;
+      }
+      *error_details = "Missing " + QuicUtils::TagToString(tag_);
+      break;
+    case QUIC_NO_ERROR:
+      has_receive_value_ = true;
+      break;
+    default:
+      *error_details = "Bad " + QuicUtils::TagToString(tag_);
+      break;
+  }
+  return error;
+}
+
+QuicFixedTagVector::QuicFixedTagVector(QuicTag name,
+                                       QuicConfigPresence presence)
+    : QuicConfigValue(name, presence),
+      has_send_values_(false),
+      has_receive_values_(false) {
+}
+
+QuicFixedTagVector::~QuicFixedTagVector() {}
+
+bool QuicFixedTagVector::HasSendValues() const {
+  return has_send_values_;
+}
+
+QuicTagVector QuicFixedTagVector::GetSendValues() const {
+  LOG_IF(DFATAL, !has_send_values_)
+      << "No send values to get for tag:" << QuicUtils::TagToString(tag_);
+  return send_values_;
+}
+
+void QuicFixedTagVector::SetSendValues(const QuicTagVector& values) {
+  has_send_values_ = true;
+  send_values_ = values;
+}
+
+bool QuicFixedTagVector::HasReceivedValues() const {
+  return has_receive_values_;
+}
+
+QuicTagVector QuicFixedTagVector::GetReceivedValues() const {
+  LOG_IF(DFATAL, !has_receive_values_)
+      << "No receive value to get for tag:" << QuicUtils::TagToString(tag_);
+  return receive_values_;
+}
+
+void QuicFixedTagVector::SetReceivedValues(const QuicTagVector& values) {
+  has_receive_values_ = true;
+  receive_values_ = values;
+}
+
+void QuicFixedTagVector::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+  if (has_send_values_) {
+    out->SetVector(tag_, send_values_);
+  }
+}
+
+QuicErrorCode QuicFixedTagVector::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+  const QuicTag* received_tags;
+  size_t received_tags_length;
+  QuicErrorCode error =
+      peer_hello.GetTaglist(tag_, &received_tags, &received_tags_length);
+  switch (error) {
+    case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+      if (presence_ == PRESENCE_OPTIONAL) {
+        return QUIC_NO_ERROR;
+      }
+      *error_details = "Missing " + QuicUtils::TagToString(tag_);
+      break;
+    case QUIC_NO_ERROR:
+      DVLOG(1) << "Received Connection Option tags from receiver.";
+      has_receive_values_ = true;
+      for (size_t i = 0; i < received_tags_length; ++i) {
+        receive_values_.push_back(received_tags[i]);
+      }
+      break;
+    default:
+      *error_details = "Bad " + QuicUtils::TagToString(tag_);
+      break;
+  }
+  return error;
+}
+
+QuicConfig::QuicConfig()
+    : max_time_before_crypto_handshake_(QuicTime::Delta::Zero()),
+      max_idle_time_before_crypto_handshake_(QuicTime::Delta::Zero()),
+      congestion_feedback_(kCGST, PRESENCE_REQUIRED),
+      connection_options_(kCOPT, PRESENCE_OPTIONAL),
+      idle_connection_state_lifetime_seconds_(kICSL, PRESENCE_REQUIRED),
+      keepalive_timeout_seconds_(kKATO, PRESENCE_OPTIONAL),
+      max_streams_per_connection_(kMSPC, PRESENCE_REQUIRED),
+      initial_congestion_window_(kSWND, PRESENCE_OPTIONAL),
+      initial_round_trip_time_us_(kIRTT, PRESENCE_OPTIONAL),
+      // TODO(rjshade): Remove this when retiring QUIC_VERSION_19.
+      initial_flow_control_window_bytes_(kIFCW, PRESENCE_OPTIONAL),
+      // TODO(rjshade): Make this PRESENCE_REQUIRED when retiring
+      // QUIC_VERSION_19.
+      initial_stream_flow_control_window_bytes_(kSFCW, PRESENCE_OPTIONAL),
+      // TODO(rjshade): Make this PRESENCE_REQUIRED when retiring
+      // QUIC_VERSION_19.
+      initial_session_flow_control_window_bytes_(kCFCW, PRESENCE_OPTIONAL),
+      socket_receive_buffer_(kSRBF, PRESENCE_OPTIONAL) {
+}
+
+QuicConfig::~QuicConfig() {}
+
+void QuicConfig::SetCongestionFeedback(
+    const QuicTagVector& congestion_feedback,
+    QuicTag default_congestion_feedback) {
+  congestion_feedback_.set(congestion_feedback, default_congestion_feedback);
+}
+
+QuicTag QuicConfig::CongestionFeedback() const {
+  return congestion_feedback_.GetTag();
+}
+
+void QuicConfig::SetConnectionOptionsToSend(
+    const QuicTagVector& connection_options) {
+  connection_options_.SetSendValues(connection_options);
+}
+
+bool QuicConfig::HasReceivedConnectionOptions() const {
+  return connection_options_.HasReceivedValues();
+}
+
+QuicTagVector QuicConfig::ReceivedConnectionOptions() const {
+  return connection_options_.GetReceivedValues();
+}
+
+bool QuicConfig::HasSendConnectionOptions() const {
+  return connection_options_.HasSendValues();
+}
+
+QuicTagVector QuicConfig::SendConnectionOptions() const {
+  return connection_options_.GetSendValues();
+}
+
+void QuicConfig::SetIdleConnectionStateLifetime(
+    QuicTime::Delta max_idle_connection_state_lifetime,
+    QuicTime::Delta default_idle_conection_state_lifetime) {
+  idle_connection_state_lifetime_seconds_.set(
+      max_idle_connection_state_lifetime.ToSeconds(),
+      default_idle_conection_state_lifetime.ToSeconds());
+}
+
+QuicTime::Delta QuicConfig::IdleConnectionStateLifetime() const {
+  return QuicTime::Delta::FromSeconds(
+      idle_connection_state_lifetime_seconds_.GetUint32());
+}
+
+QuicTime::Delta QuicConfig::KeepaliveTimeout() const {
+  return QuicTime::Delta::FromSeconds(
+      keepalive_timeout_seconds_.GetUint32());
+}
+
+void QuicConfig::SetMaxStreamsPerConnection(size_t max_streams,
+                                            size_t default_streams) {
+  max_streams_per_connection_.set(max_streams, default_streams);
+}
+
+uint32 QuicConfig::MaxStreamsPerConnection() const {
+  return max_streams_per_connection_.GetUint32();
+}
+
+void QuicConfig::SetInitialCongestionWindowToSend(size_t initial_window) {
+  initial_congestion_window_.SetSendValue(initial_window);
+}
+
+bool QuicConfig::HasReceivedInitialCongestionWindow() const {
+  return initial_congestion_window_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedInitialCongestionWindow() const {
+  return initial_congestion_window_.GetReceivedValue();
+}
+
+void QuicConfig::SetInitialRoundTripTimeUsToSend(size_t rtt) {
+  initial_round_trip_time_us_.SetSendValue(rtt);
+}
+
+bool QuicConfig::HasReceivedInitialRoundTripTimeUs() const {
+  return initial_round_trip_time_us_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedInitialRoundTripTimeUs() const {
+  return initial_round_trip_time_us_.GetReceivedValue();
+}
+
+bool QuicConfig::HasInitialRoundTripTimeUsToSend() const {
+  return initial_round_trip_time_us_.HasSendValue();
+}
+
+uint32 QuicConfig::GetInitialRoundTripTimeUsToSend() const {
+  return initial_round_trip_time_us_.GetSendValue();
+}
+
+void QuicConfig::SetInitialFlowControlWindowToSend(uint32 window_bytes) {
+  if (window_bytes < kDefaultFlowControlSendWindow) {
+    LOG(DFATAL) << "Initial flow control receive window (" << window_bytes
+                << ") cannot be set lower than default ("
+                << kDefaultFlowControlSendWindow << ").";
+    window_bytes = kDefaultFlowControlSendWindow;
+  }
+  initial_flow_control_window_bytes_.SetSendValue(window_bytes);
+}
+
+uint32 QuicConfig::GetInitialFlowControlWindowToSend() const {
+  return initial_flow_control_window_bytes_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedInitialFlowControlWindowBytes() const {
+  return initial_flow_control_window_bytes_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedInitialFlowControlWindowBytes() const {
+  return initial_flow_control_window_bytes_.GetReceivedValue();
+}
+
+void QuicConfig::SetInitialStreamFlowControlWindowToSend(uint32 window_bytes) {
+  if (window_bytes < kDefaultFlowControlSendWindow) {
+    LOG(DFATAL) << "Initial stream flow control receive window ("
+                << window_bytes << ") cannot be set lower than default ("
+                << kDefaultFlowControlSendWindow << ").";
+    window_bytes = kDefaultFlowControlSendWindow;
+  }
+  initial_stream_flow_control_window_bytes_.SetSendValue(window_bytes);
+}
+
+uint32 QuicConfig::GetInitialStreamFlowControlWindowToSend() const {
+  return initial_stream_flow_control_window_bytes_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedInitialStreamFlowControlWindowBytes() const {
+  return initial_stream_flow_control_window_bytes_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedInitialStreamFlowControlWindowBytes() const {
+  return initial_stream_flow_control_window_bytes_.GetReceivedValue();
+}
+
+void QuicConfig::SetInitialSessionFlowControlWindowToSend(uint32 window_bytes) {
+  if (window_bytes < kDefaultFlowControlSendWindow) {
+    LOG(DFATAL) << "Initial session flow control receive window ("
+                << window_bytes << ") cannot be set lower than default ("
+                << kDefaultFlowControlSendWindow << ").";
+    window_bytes = kDefaultFlowControlSendWindow;
+  }
+  initial_session_flow_control_window_bytes_.SetSendValue(window_bytes);
+}
+
+uint32 QuicConfig::GetInitialSessionFlowControlWindowToSend() const {
+  return initial_session_flow_control_window_bytes_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedInitialSessionFlowControlWindowBytes() const {
+  return initial_session_flow_control_window_bytes_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedInitialSessionFlowControlWindowBytes() const {
+  return initial_session_flow_control_window_bytes_.GetReceivedValue();
+}
+
+void QuicConfig::SetSocketReceiveBufferToSend(uint32 tcp_receive_window) {
+  socket_receive_buffer_.SetSendValue(tcp_receive_window);
+}
+
+uint32 QuicConfig::GetSocketReceiveBufferToSend() const {
+  return socket_receive_buffer_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedSocketReceiveBuffer() const {
+  return socket_receive_buffer_.HasReceivedValue();
+}
+
+uint32 QuicConfig::ReceivedSocketReceiveBuffer() const {
+  return socket_receive_buffer_.GetReceivedValue();
+}
+
+bool QuicConfig::negotiated() const {
+  // TODO(ianswett): Add the negotiated parameters once and iterate over all
+  // of them in negotiated, ToHandshakeMessage, ProcessClientHello, and
+  // ProcessServerHello.
+  return congestion_feedback_.negotiated() &&
+      idle_connection_state_lifetime_seconds_.negotiated() &&
+      keepalive_timeout_seconds_.negotiated() &&
+      max_streams_per_connection_.negotiated();
+}
+
+void QuicConfig::SetDefaults() {
+  QuicTagVector congestion_feedback;
+  congestion_feedback.push_back(kQBIC);
+  congestion_feedback_.set(congestion_feedback, kQBIC);
+  idle_connection_state_lifetime_seconds_.set(kMaximumIdleTimeoutSecs,
+                                              kDefaultIdleTimeoutSecs);
+  // kKATO is optional. Return 0 if not negotiated.
+  keepalive_timeout_seconds_.set(0, 0);
+  SetMaxStreamsPerConnection(kDefaultMaxStreamsPerConnection,
+                             kDefaultMaxStreamsPerConnection);
+  max_time_before_crypto_handshake_ =
+      QuicTime::Delta::FromSeconds(kMaxTimeForCryptoHandshakeSecs);
+  max_idle_time_before_crypto_handshake_ =
+      QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs);
+
+  SetInitialFlowControlWindowToSend(kDefaultFlowControlSendWindow);
+  SetInitialStreamFlowControlWindowToSend(kDefaultFlowControlSendWindow);
+  SetInitialSessionFlowControlWindowToSend(kDefaultFlowControlSendWindow);
+}
+
+void QuicConfig::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+  congestion_feedback_.ToHandshakeMessage(out);
+  idle_connection_state_lifetime_seconds_.ToHandshakeMessage(out);
+  keepalive_timeout_seconds_.ToHandshakeMessage(out);
+  max_streams_per_connection_.ToHandshakeMessage(out);
+  initial_congestion_window_.ToHandshakeMessage(out);
+  initial_round_trip_time_us_.ToHandshakeMessage(out);
+  initial_flow_control_window_bytes_.ToHandshakeMessage(out);
+  initial_stream_flow_control_window_bytes_.ToHandshakeMessage(out);
+  initial_session_flow_control_window_bytes_.ToHandshakeMessage(out);
+  socket_receive_buffer_.ToHandshakeMessage(out);
+  connection_options_.ToHandshakeMessage(out);
+}
+
+QuicErrorCode QuicConfig::ProcessPeerHello(
+    const CryptoHandshakeMessage& peer_hello,
+    HelloType hello_type,
+    string* error_details) {
+  DCHECK(error_details != nullptr);
+
+  QuicErrorCode error = QUIC_NO_ERROR;
+  if (error == QUIC_NO_ERROR) {
+    error = congestion_feedback_.ProcessPeerHello(
+        peer_hello,  hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = idle_connection_state_lifetime_seconds_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = keepalive_timeout_seconds_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = max_streams_per_connection_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = initial_congestion_window_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = initial_round_trip_time_us_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = initial_flow_control_window_bytes_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = initial_stream_flow_control_window_bytes_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = initial_session_flow_control_window_bytes_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = socket_receive_buffer_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = connection_options_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  return error;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_config.h b/net/quic/quic_config.h
new file mode 100644
index 0000000..203eb2e
--- /dev/null
+++ b/net/quic/quic_config.h
@@ -0,0 +1,417 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CONFIG_H_
+#define NET_QUIC_QUIC_CONFIG_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+namespace test {
+class QuicConfigPeer;
+}  // namespace test
+
+class CryptoHandshakeMessage;
+
+// Describes whether or not a given QuicTag is required or optional in the
+// handshake message.
+enum QuicConfigPresence {
+  // This negotiable value can be absent from the handshake message. Default
+  // value is selected as the negotiated value in such a case.
+  PRESENCE_OPTIONAL,
+  // This negotiable value is required in the handshake message otherwise the
+  // Process*Hello function returns an error.
+  PRESENCE_REQUIRED,
+};
+
+// Whether the CryptoHandshakeMessage is from the client or server.
+enum HelloType {
+  CLIENT,
+  SERVER,
+};
+
+// An abstract base class that stores a value that can be sent in CHLO/SHLO
+// message. These values can be OPTIONAL or REQUIRED, depending on |presence_|.
+class NET_EXPORT_PRIVATE QuicConfigValue {
+ public:
+  QuicConfigValue(QuicTag tag, QuicConfigPresence presence);
+  virtual ~QuicConfigValue();
+
+  // Serialises tag name and value(s) to |out|.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const = 0;
+
+  // Selects a mutually acceptable value from those offered in |peer_hello|
+  // and those defined in the subclass.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) = 0;
+
+ protected:
+  const QuicTag tag_;
+  const QuicConfigPresence presence_;
+};
+
+class NET_EXPORT_PRIVATE QuicNegotiableValue : public QuicConfigValue {
+ public:
+  QuicNegotiableValue(QuicTag tag, QuicConfigPresence presence);
+  virtual ~QuicNegotiableValue();
+
+  bool negotiated() const {
+    return negotiated_;
+  }
+
+ protected:
+  bool negotiated_;
+};
+
+class NET_EXPORT_PRIVATE QuicNegotiableUint32 : public QuicNegotiableValue {
+ public:
+  // Default and max values default to 0.
+  QuicNegotiableUint32(QuicTag name, QuicConfigPresence presence);
+  virtual ~QuicNegotiableUint32();
+
+  // Sets the maximum possible value that can be achieved after negotiation and
+  // also the default values to be assumed if PRESENCE_OPTIONAL and the *HLO msg
+  // doesn't contain a value corresponding to |name_|. |max| is serialised via
+  // ToHandshakeMessage call if |negotiated_| is false.
+  void set(uint32 max, uint32 default_value);
+
+  // Returns the value negotiated if |negotiated_| is true, otherwise returns
+  // default_value_ (used to set default values before negotiation finishes).
+  uint32 GetUint32() const;
+
+  // Serialises |name_| and value to |out|. If |negotiated_| is true then
+  // |negotiated_value_| is serialised, otherwise |max_value_| is serialised.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const OVERRIDE;
+
+  // Sets |negotiated_value_| to the minimum of |max_value_| and the
+  // corresponding value from |peer_hello|. If the corresponding value is
+  // missing and PRESENCE_OPTIONAL then |negotiated_value_| is set to
+  // |default_value_|.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) OVERRIDE;
+
+ private:
+  uint32 max_value_;
+  uint32 default_value_;
+  uint32 negotiated_value_;
+};
+
+class NET_EXPORT_PRIVATE QuicNegotiableTag : public QuicNegotiableValue {
+ public:
+  QuicNegotiableTag(QuicTag name, QuicConfigPresence presence);
+  virtual ~QuicNegotiableTag();
+
+  // Sets the possible values that |negotiated_tag_| can take after negotiation
+  // and the default value that |negotiated_tag_| takes if OPTIONAL and *HLO
+  // msg doesn't contain tag |name_|.
+  void set(const QuicTagVector& possible_values, QuicTag default_value);
+
+  // Returns the negotiated tag if |negotiated_| is true, otherwise returns
+  // |default_value_| (used to set default values before negotiation finishes).
+  QuicTag GetTag() const;
+
+  // Serialises |name_| and vector (either possible or negotiated) to |out|. If
+  // |negotiated_| is true then |negotiated_tag_| is serialised, otherwise
+  // |possible_values_| is serialised.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const OVERRIDE;
+
+  // Selects the tag common to both tags in |client_hello| for |name_| and
+  // |possible_values_| with preference to tag in |possible_values_|. The
+  // selected tag is set as |negotiated_tag_|.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) OVERRIDE;
+
+ private:
+  // Reads the vector corresponding to |name_| from |msg| into |out|. If the
+  // |name_| is absent in |msg| and |presence_| is set to OPTIONAL |out| is set
+  // to |possible_values_|.
+  QuicErrorCode ReadVector(const CryptoHandshakeMessage& msg,
+                           const QuicTag** out,
+                           size_t* out_length,
+                           std::string* error_details) const;
+
+  QuicTag negotiated_tag_;
+  QuicTagVector possible_values_;
+  QuicTag default_value_;
+};
+
+// Stores uint32 from CHLO or SHLO messages that are not negotiated.
+class NET_EXPORT_PRIVATE QuicFixedUint32 : public QuicConfigValue {
+ public:
+  QuicFixedUint32(QuicTag name, QuicConfigPresence presence);
+  virtual ~QuicFixedUint32();
+
+  bool HasSendValue() const;
+
+  uint32 GetSendValue() const;
+
+  void SetSendValue(uint32 value);
+
+  bool HasReceivedValue() const;
+
+  uint32 GetReceivedValue() const;
+
+  void SetReceivedValue(uint32 value);
+
+  // If has_send_value is true, serialises |tag_| and |send_value_| to |out|.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const OVERRIDE;
+
+  // Sets |value_| to the corresponding value from |peer_hello_| if it exists.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) OVERRIDE;
+
+ private:
+  uint32 send_value_;
+  bool has_send_value_;
+  uint32 receive_value_;
+  bool has_receive_value_;
+};
+
+// Stores tag from CHLO or SHLO messages that are not negotiated.
+class NET_EXPORT_PRIVATE QuicFixedTag : public QuicConfigValue {
+ public:
+  QuicFixedTag(QuicTag name, QuicConfigPresence presence);
+  virtual ~QuicFixedTag();
+
+  bool HasSendValue() const;
+
+  QuicTag GetSendValue() const;
+
+  void SetSendValue(QuicTag value);
+
+  bool HasReceivedValue() const;
+
+  QuicTag GetReceivedValue() const;
+
+  void SetReceivedValue(QuicTag value);
+
+  // If has_send_value is true, serialises |tag_| and |send_value_| to |out|.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const OVERRIDE;
+
+  // Sets |value_| to the corresponding value from |client_hello_| if it exists.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) OVERRIDE;
+
+ private:
+  QuicTag send_value_;
+  bool has_send_value_;
+  QuicTag receive_value_;
+  bool has_receive_value_;
+};
+
+// Stores tag from CHLO or SHLO messages that are not negotiated.
+class NET_EXPORT_PRIVATE QuicFixedTagVector : public QuicConfigValue {
+ public:
+  QuicFixedTagVector(QuicTag name, QuicConfigPresence presence);
+  virtual ~QuicFixedTagVector();
+
+  bool HasSendValues() const;
+
+  QuicTagVector GetSendValues() const;
+
+  void SetSendValues(const QuicTagVector& values);
+
+  bool HasReceivedValues() const;
+
+  QuicTagVector GetReceivedValues() const;
+
+  void SetReceivedValues(const QuicTagVector& values);
+
+  // If has_send_value is true, serialises |tag_vector_| and |send_value_| to
+  // |out|.
+  virtual void ToHandshakeMessage(CryptoHandshakeMessage* out) const OVERRIDE;
+
+  // Sets |receive_values_| to the corresponding value from |client_hello_| if
+  // it exists.
+  virtual QuicErrorCode ProcessPeerHello(
+      const CryptoHandshakeMessage& peer_hello,
+      HelloType hello_type,
+      std::string* error_details) OVERRIDE;
+
+ private:
+  QuicTagVector send_values_;
+  bool has_send_values_;
+  QuicTagVector receive_values_;
+  bool has_receive_values_;
+};
+
+// QuicConfig contains non-crypto configuration options that are negotiated in
+// the crypto handshake.
+class NET_EXPORT_PRIVATE QuicConfig {
+ public:
+  QuicConfig();
+  ~QuicConfig();
+
+  void SetCongestionFeedback(const QuicTagVector& congestion_feedback,
+                             QuicTag default_congestion_feedback);
+
+  QuicTag CongestionFeedback() const;
+
+  void SetConnectionOptionsToSend(const QuicTagVector& connection_options);
+
+  bool HasReceivedConnectionOptions() const;
+
+  QuicTagVector ReceivedConnectionOptions() const;
+
+  bool HasSendConnectionOptions() const;
+
+  QuicTagVector SendConnectionOptions() const;
+
+  void SetIdleConnectionStateLifetime(
+      QuicTime::Delta max_idle_connection_state_lifetime,
+      QuicTime::Delta default_idle_conection_state_lifetime);
+
+  QuicTime::Delta IdleConnectionStateLifetime() const;
+
+  QuicTime::Delta KeepaliveTimeout() const;
+
+  void SetMaxStreamsPerConnection(size_t max_streams, size_t default_streams);
+
+  uint32 MaxStreamsPerConnection() const;
+
+  void set_max_time_before_crypto_handshake(
+      QuicTime::Delta max_time_before_crypto_handshake) {
+    max_time_before_crypto_handshake_ = max_time_before_crypto_handshake;
+  }
+
+  QuicTime::Delta max_time_before_crypto_handshake() const {
+    return max_time_before_crypto_handshake_;
+  }
+
+  void set_max_idle_time_before_crypto_handshake(
+      QuicTime::Delta max_idle_time_before_crypto_handshake) {
+    max_idle_time_before_crypto_handshake_ =
+        max_idle_time_before_crypto_handshake;
+  }
+
+  QuicTime::Delta max_idle_time_before_crypto_handshake() const {
+    return max_idle_time_before_crypto_handshake_;
+  }
+
+  // Sets the peer's default initial congestion window in packets.
+  void SetInitialCongestionWindowToSend(size_t initial_window);
+
+  bool HasReceivedInitialCongestionWindow() const;
+
+  uint32 ReceivedInitialCongestionWindow() const;
+
+  // Sets an estimated initial round trip time in us.
+  void SetInitialRoundTripTimeUsToSend(size_t rtt_us);
+
+  bool HasReceivedInitialRoundTripTimeUs() const;
+
+  uint32 ReceivedInitialRoundTripTimeUs() const;
+
+  bool HasInitialRoundTripTimeUsToSend() const;
+
+  uint32 GetInitialRoundTripTimeUsToSend() const;
+
+  // TODO(rjshade): Remove all InitialFlowControlWindow methods when removing
+  // QUIC_VERSION_19.
+  // Sets an initial stream flow control window size to transmit to the peer.
+  void SetInitialFlowControlWindowToSend(uint32 window_bytes);
+
+  uint32 GetInitialFlowControlWindowToSend() const;
+
+  bool HasReceivedInitialFlowControlWindowBytes() const;
+
+  uint32 ReceivedInitialFlowControlWindowBytes() const;
+
+  // Sets an initial stream flow control window size to transmit to the peer.
+  void SetInitialStreamFlowControlWindowToSend(uint32 window_bytes);
+
+  uint32 GetInitialStreamFlowControlWindowToSend() const;
+
+  bool HasReceivedInitialStreamFlowControlWindowBytes() const;
+
+  uint32 ReceivedInitialStreamFlowControlWindowBytes() const;
+
+  // Sets an initial session flow control window size to transmit to the peer.
+  void SetInitialSessionFlowControlWindowToSend(uint32 window_bytes);
+
+  uint32 GetInitialSessionFlowControlWindowToSend() const;
+
+  bool HasReceivedInitialSessionFlowControlWindowBytes() const;
+
+  uint32 ReceivedInitialSessionFlowControlWindowBytes() const;
+
+  // Sets socket receive buffer to transmit to the peer.
+  void SetSocketReceiveBufferToSend(uint32 window_bytes);
+
+  uint32 GetSocketReceiveBufferToSend() const;
+
+  bool HasReceivedSocketReceiveBuffer() const;
+
+  uint32 ReceivedSocketReceiveBuffer() const;
+
+  bool negotiated() const;
+
+  // SetDefaults sets the members to sensible, default values.
+  void SetDefaults();
+
+  // ToHandshakeMessage serialises the settings in this object as a series of
+  // tags /value pairs and adds them to |out|.
+  void ToHandshakeMessage(CryptoHandshakeMessage* out) const;
+
+  // Calls ProcessPeerHello on each negotiable parameter. On failure returns
+  // the corresponding QuicErrorCode and sets detailed error in |error_details|.
+  QuicErrorCode ProcessPeerHello(const CryptoHandshakeMessage& peer_hello,
+                                 HelloType hello_type,
+                                 std::string* error_details);
+
+ private:
+  friend class test::QuicConfigPeer;
+
+  // Configurations options that are not negotiated.
+  // Maximum time the session can be alive before crypto handshake is finished.
+  QuicTime::Delta max_time_before_crypto_handshake_;
+  // Maximum idle time before the crypto handshake has completed.
+  QuicTime::Delta max_idle_time_before_crypto_handshake_;
+
+  // Congestion control feedback type.
+  QuicNegotiableTag congestion_feedback_;
+  // Connection options.
+  QuicFixedTagVector connection_options_;
+  // Idle connection state lifetime
+  QuicNegotiableUint32 idle_connection_state_lifetime_seconds_;
+  // Keepalive timeout, or 0 to turn off keepalive probes
+  QuicNegotiableUint32 keepalive_timeout_seconds_;
+  // Maximum number of streams that the connection can support.
+  QuicNegotiableUint32 max_streams_per_connection_;
+  // Initial congestion window in packets.
+  QuicFixedUint32 initial_congestion_window_;
+  // Initial round trip time estimate in microseconds.
+  QuicFixedUint32 initial_round_trip_time_us_;
+
+  // TODO(rjshade): Remove when removing QUIC_VERSION_19.
+  // Initial flow control receive window in bytes.
+  QuicFixedUint32 initial_flow_control_window_bytes_;
+
+  // Initial stream flow control receive window in bytes.
+  QuicFixedUint32 initial_stream_flow_control_window_bytes_;
+  // Initial session flow control receive window in bytes.
+  QuicFixedUint32 initial_session_flow_control_window_bytes_;
+
+  // Socket receive buffer in bytes.
+  QuicFixedUint32 socket_receive_buffer_;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CONFIG_H_
diff --git a/net/quic/quic_config_test.cc b/net/quic/quic_config_test.cc
new file mode 100644
index 0000000..2263e44
--- /dev/null
+++ b/net/quic/quic_config_test.cc
@@ -0,0 +1,296 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_config.h"
+
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicConfigTest : public ::testing::Test {
+ protected:
+  QuicConfigTest() {
+    config_.SetDefaults();
+  }
+
+  QuicConfig config_;
+};
+
+TEST_F(QuicConfigTest, ToHandshakeMessage) {
+  config_.SetDefaults();
+  config_.SetInitialFlowControlWindowToSend(
+      kInitialSessionFlowControlWindowForTest);
+  config_.SetInitialStreamFlowControlWindowToSend(
+      kInitialStreamFlowControlWindowForTest);
+  config_.SetInitialSessionFlowControlWindowToSend(
+      kInitialSessionFlowControlWindowForTest);
+  config_.SetIdleConnectionStateLifetime(QuicTime::Delta::FromSeconds(5),
+                                         QuicTime::Delta::FromSeconds(2));
+  config_.SetMaxStreamsPerConnection(4, 2);
+  config_.SetSocketReceiveBufferToSend(kDefaultSocketReceiveBuffer);
+  CryptoHandshakeMessage msg;
+  config_.ToHandshakeMessage(&msg);
+
+  uint32 value;
+  QuicErrorCode error = msg.GetUint32(kICSL, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(5u, value);
+
+  error = msg.GetUint32(kMSPC, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(4u, value);
+
+  error = msg.GetUint32(kIFCW, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(kInitialSessionFlowControlWindowForTest, value);
+
+  error = msg.GetUint32(kSFCW, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(kInitialStreamFlowControlWindowForTest, value);
+
+  error = msg.GetUint32(kCFCW, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(kInitialSessionFlowControlWindowForTest, value);
+
+  error = msg.GetUint32(kSRBF, &value);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_EQ(kDefaultSocketReceiveBuffer, value);
+
+  const QuicTag* out;
+  size_t out_len;
+  error = msg.GetTaglist(kCGST, &out, &out_len);
+  EXPECT_EQ(1u, out_len);
+  EXPECT_EQ(kQBIC, *out);
+}
+
+TEST_F(QuicConfigTest, ProcessClientHello) {
+  QuicConfig client_config;
+  QuicTagVector cgst;
+  cgst.push_back(kQBIC);
+  client_config.SetCongestionFeedback(cgst, kQBIC);
+  client_config.SetIdleConnectionStateLifetime(
+      QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs),
+      QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs));
+  client_config.SetMaxStreamsPerConnection(
+      2 * kDefaultMaxStreamsPerConnection, kDefaultMaxStreamsPerConnection);
+  client_config.SetInitialRoundTripTimeUsToSend(
+      10 * base::Time::kMicrosecondsPerMillisecond);
+  client_config.SetInitialFlowControlWindowToSend(
+      2 * kInitialSessionFlowControlWindowForTest);
+  client_config.SetInitialStreamFlowControlWindowToSend(
+      2 * kInitialStreamFlowControlWindowForTest);
+  client_config.SetInitialSessionFlowControlWindowToSend(
+      2 * kInitialSessionFlowControlWindowForTest);
+  client_config.SetSocketReceiveBufferToSend(kDefaultSocketReceiveBuffer);
+  QuicTagVector copt;
+  copt.push_back(kTBBR);
+  copt.push_back(kFHDR);
+  client_config.SetConnectionOptionsToSend(copt);
+  CryptoHandshakeMessage msg;
+  client_config.ToHandshakeMessage(&msg);
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, CLIENT, &error_details);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_TRUE(config_.negotiated());
+  EXPECT_EQ(kQBIC, config_.CongestionFeedback());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs),
+            config_.IdleConnectionStateLifetime());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection,
+            config_.MaxStreamsPerConnection());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(0), config_.KeepaliveTimeout());
+  EXPECT_EQ(10 * base::Time::kMicrosecondsPerMillisecond,
+            config_.ReceivedInitialRoundTripTimeUs());
+  EXPECT_TRUE(config_.HasReceivedConnectionOptions());
+  EXPECT_EQ(2u, config_.ReceivedConnectionOptions().size());
+  EXPECT_EQ(config_.ReceivedConnectionOptions()[0], kTBBR);
+  EXPECT_EQ(config_.ReceivedConnectionOptions()[1], kFHDR);
+  EXPECT_EQ(config_.ReceivedInitialFlowControlWindowBytes(),
+            2 * kInitialSessionFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedInitialStreamFlowControlWindowBytes(),
+            2 * kInitialStreamFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedInitialSessionFlowControlWindowBytes(),
+            2 * kInitialSessionFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedSocketReceiveBuffer(),
+            kDefaultSocketReceiveBuffer);
+}
+
+TEST_F(QuicConfigTest, ProcessServerHello) {
+  QuicConfig server_config;
+  QuicTagVector cgst;
+  cgst.push_back(kQBIC);
+  server_config.SetCongestionFeedback(cgst, kQBIC);
+  server_config.SetIdleConnectionStateLifetime(
+      QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs / 2),
+      QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs / 2));
+  server_config.SetMaxStreamsPerConnection(
+      kDefaultMaxStreamsPerConnection / 2,
+      kDefaultMaxStreamsPerConnection / 2);
+  server_config.SetInitialCongestionWindowToSend(kDefaultInitialWindow / 2);
+  server_config.SetInitialRoundTripTimeUsToSend(
+      10 * base::Time::kMicrosecondsPerMillisecond);
+  server_config.SetInitialFlowControlWindowToSend(
+      2 * kInitialSessionFlowControlWindowForTest);
+  server_config.SetInitialStreamFlowControlWindowToSend(
+      2 * kInitialStreamFlowControlWindowForTest);
+  server_config.SetInitialSessionFlowControlWindowToSend(
+      2 * kInitialSessionFlowControlWindowForTest);
+  server_config.SetSocketReceiveBufferToSend(kDefaultSocketReceiveBuffer);
+  CryptoHandshakeMessage msg;
+  server_config.ToHandshakeMessage(&msg);
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, SERVER, &error_details);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+  EXPECT_TRUE(config_.negotiated());
+  EXPECT_EQ(kQBIC, config_.CongestionFeedback());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs / 2),
+            config_.IdleConnectionStateLifetime());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection / 2,
+            config_.MaxStreamsPerConnection());
+  EXPECT_EQ(kDefaultInitialWindow / 2,
+            config_.ReceivedInitialCongestionWindow());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(0), config_.KeepaliveTimeout());
+  EXPECT_EQ(10 * base::Time::kMicrosecondsPerMillisecond,
+            config_.ReceivedInitialRoundTripTimeUs());
+  EXPECT_EQ(config_.ReceivedInitialFlowControlWindowBytes(),
+            2 * kInitialSessionFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedInitialStreamFlowControlWindowBytes(),
+            2 * kInitialStreamFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedInitialSessionFlowControlWindowBytes(),
+            2 * kInitialSessionFlowControlWindowForTest);
+  EXPECT_EQ(config_.ReceivedSocketReceiveBuffer(),
+            kDefaultSocketReceiveBuffer);
+}
+
+TEST_F(QuicConfigTest, MissingOptionalValuesInCHLO) {
+  CryptoHandshakeMessage msg;
+  msg.SetValue(kICSL, 1);
+  msg.SetVector(kCGST, QuicTagVector(1, kQBIC));
+
+  // Set all REQUIRED tags.
+  msg.SetValue(kICSL, 1);
+  msg.SetVector(kCGST, QuicTagVector(1, kQBIC));
+  msg.SetValue(kMSPC, 1);
+
+  // No error, as rest are optional.
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, CLIENT, &error_details);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+
+  EXPECT_FALSE(config_.HasReceivedInitialFlowControlWindowBytes());
+}
+
+TEST_F(QuicConfigTest, MissingOptionalValuesInSHLO) {
+  CryptoHandshakeMessage msg;
+
+  // Set all REQUIRED tags.
+  msg.SetValue(kICSL, 1);
+  msg.SetVector(kCGST, QuicTagVector(1, kQBIC));
+  msg.SetValue(kMSPC, 1);
+
+  // No error, as rest are optional.
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, SERVER, &error_details);
+  EXPECT_EQ(QUIC_NO_ERROR, error);
+
+  EXPECT_FALSE(config_.HasReceivedInitialFlowControlWindowBytes());
+}
+
+TEST_F(QuicConfigTest, MissingValueInCHLO) {
+  CryptoHandshakeMessage msg;
+  msg.SetValue(kICSL, 1);
+  msg.SetVector(kCGST, QuicTagVector(1, kQBIC));
+  // Missing kMSPC. KATO is optional.
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, CLIENT, &error_details);
+  EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
+}
+
+TEST_F(QuicConfigTest, MissingValueInSHLO) {
+  CryptoHandshakeMessage msg;
+  msg.SetValue(kICSL, 1);
+  msg.SetValue(kMSPC, 3);
+  // Missing CGST. KATO is optional.
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, SERVER, &error_details);
+  EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
+}
+
+TEST_F(QuicConfigTest, OutOfBoundSHLO) {
+  QuicConfig server_config;
+  server_config.SetIdleConnectionStateLifetime(
+      QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs),
+      QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs));
+
+  CryptoHandshakeMessage msg;
+  server_config.ToHandshakeMessage(&msg);
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, SERVER, &error_details);
+  EXPECT_EQ(QUIC_INVALID_NEGOTIATED_VALUE, error);
+}
+
+TEST_F(QuicConfigTest, MultipleNegotiatedValuesInVectorTag) {
+  QuicConfig server_config;
+  QuicTagVector cgst;
+  cgst.push_back(kQBIC);
+  cgst.push_back(kTBBR);
+  server_config.SetCongestionFeedback(cgst, kQBIC);
+
+  CryptoHandshakeMessage msg;
+  server_config.ToHandshakeMessage(&msg);
+  string error_details;
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, SERVER, &error_details);
+  EXPECT_EQ(QUIC_INVALID_NEGOTIATED_VALUE, error);
+}
+
+TEST_F(QuicConfigTest, NoOverLapInCGST) {
+  QuicConfig server_config;
+  server_config.SetDefaults();
+  QuicTagVector cgst;
+  cgst.push_back(kTBBR);
+  server_config.SetCongestionFeedback(cgst, kTBBR);
+
+  CryptoHandshakeMessage msg;
+  string error_details;
+  server_config.ToHandshakeMessage(&msg);
+  const QuicErrorCode error =
+      config_.ProcessPeerHello(msg, CLIENT, &error_details);
+  DVLOG(1) << QuicUtils::ErrorToString(error);
+  EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP, error);
+}
+
+TEST_F(QuicConfigTest, InvalidFlowControlWindow) {
+  // QuicConfig should not accept an invalid flow control window to send to the
+  // peer: the receive window must be at least the default of 16 Kb.
+  QuicConfig config;
+  const uint64 kInvalidWindow = kDefaultFlowControlSendWindow - 1;
+  EXPECT_DFATAL(config.SetInitialFlowControlWindowToSend(kInvalidWindow),
+                "Initial flow control receive window");
+
+  EXPECT_EQ(kDefaultFlowControlSendWindow,
+            config.GetInitialFlowControlWindowToSend());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_connection.cc b/net/quic/quic_connection.cc
new file mode 100644
index 0000000..8d49010
--- /dev/null
+++ b/net/quic/quic_connection.cc
@@ -0,0 +1,2041 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <set>
+#include <utility>
+
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/base/net_errors.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/iovector.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using base::hash_map;
+using base::hash_set;
+using std::list;
+using std::make_pair;
+using std::max;
+using std::min;
+using std::numeric_limits;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace net {
+
+class QuicDecrypter;
+class QuicEncrypter;
+
+namespace {
+
+// The largest gap in packets we'll accept without closing the connection.
+// This will likely have to be tuned.
+const QuicPacketSequenceNumber kMaxPacketGap = 5000;
+
+// Limit the number of FEC groups to two.  If we get enough out of order packets
+// that this becomes limiting, we can revisit.
+const size_t kMaxFecGroups = 2;
+
+// Limit the number of undecryptable packets we buffer in
+// expectation of the CHLO/SHLO arriving.
+const size_t kMaxUndecryptablePackets = 10;
+
+// Maximum number of acks received before sending an ack in response.
+const size_t kMaxPacketsReceivedBeforeAckSend = 20;
+
+bool Near(QuicPacketSequenceNumber a, QuicPacketSequenceNumber b) {
+  QuicPacketSequenceNumber delta = (a > b) ? a - b : b - a;
+  return delta <= kMaxPacketGap;
+}
+
+// An alarm that is scheduled to send an ack if a timeout occurs.
+class AckAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit AckAlarm(QuicConnection* connection)
+      : connection_(connection) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    connection_->SendAck();
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicConnection* connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(AckAlarm);
+};
+
+// This alarm will be scheduled any time a data-bearing packet is sent out.
+// When the alarm goes off, the connection checks to see if the oldest packets
+// have been acked, and retransmit them if they have not.
+class RetransmissionAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit RetransmissionAlarm(QuicConnection* connection)
+      : connection_(connection) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    connection_->OnRetransmissionTimeout();
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicConnection* connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(RetransmissionAlarm);
+};
+
+// An alarm that is scheduled when the sent scheduler requires a
+// a delay before sending packets and fires when the packet may be sent.
+class SendAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit SendAlarm(QuicConnection* connection)
+      : connection_(connection) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    connection_->WriteIfNotBlocked();
+    // Never reschedule the alarm, since CanWrite does that.
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicConnection* connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(SendAlarm);
+};
+
+class TimeoutAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit TimeoutAlarm(QuicConnection* connection)
+      : connection_(connection) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    connection_->CheckForTimeout();
+    // Never reschedule the alarm, since CheckForTimeout does that.
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicConnection* connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(TimeoutAlarm);
+};
+
+class PingAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit PingAlarm(QuicConnection* connection)
+      : connection_(connection) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    connection_->SendPing();
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicConnection* connection_;
+
+  DISALLOW_COPY_AND_ASSIGN(PingAlarm);
+};
+
+}  // namespace
+
+QuicConnection::QueuedPacket::QueuedPacket(SerializedPacket packet,
+                                           EncryptionLevel level)
+  : serialized_packet(packet),
+    encryption_level(level),
+    transmission_type(NOT_RETRANSMISSION),
+    original_sequence_number(0) {
+}
+
+QuicConnection::QueuedPacket::QueuedPacket(
+    SerializedPacket packet,
+    EncryptionLevel level,
+    TransmissionType transmission_type,
+    QuicPacketSequenceNumber original_sequence_number)
+    : serialized_packet(packet),
+      encryption_level(level),
+      transmission_type(transmission_type),
+      original_sequence_number(original_sequence_number) {
+}
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+QuicConnection::QuicConnection(QuicConnectionId connection_id,
+                               IPEndPoint address,
+                               QuicConnectionHelperInterface* helper,
+                               const PacketWriterFactory& writer_factory,
+                               bool owns_writer,
+                               bool is_server,
+                               const QuicVersionVector& supported_versions)
+    : framer_(supported_versions, helper->GetClock()->ApproximateNow(),
+              is_server),
+      helper_(helper),
+      writer_(writer_factory.Create(this)),
+      owns_writer_(owns_writer),
+      encryption_level_(ENCRYPTION_NONE),
+      clock_(helper->GetClock()),
+      random_generator_(helper->GetRandomGenerator()),
+      connection_id_(connection_id),
+      peer_address_(address),
+      migrating_peer_port_(0),
+      last_packet_revived_(false),
+      last_size_(0),
+      last_decrypted_packet_level_(ENCRYPTION_NONE),
+      largest_seen_packet_with_ack_(0),
+      largest_seen_packet_with_stop_waiting_(0),
+      pending_version_negotiation_packet_(false),
+      received_packet_manager_(&stats_),
+      ack_queued_(false),
+      num_packets_received_since_last_ack_sent_(0),
+      stop_waiting_count_(0),
+      ack_alarm_(helper->CreateAlarm(new AckAlarm(this))),
+      retransmission_alarm_(helper->CreateAlarm(new RetransmissionAlarm(this))),
+      send_alarm_(helper->CreateAlarm(new SendAlarm(this))),
+      resume_writes_alarm_(helper->CreateAlarm(new SendAlarm(this))),
+      timeout_alarm_(helper->CreateAlarm(new TimeoutAlarm(this))),
+      ping_alarm_(helper->CreateAlarm(new PingAlarm(this))),
+      packet_generator_(connection_id_, &framer_, random_generator_, this),
+      idle_network_timeout_(FLAGS_quic_unified_timeouts ?
+          QuicTime::Delta::Infinite() :
+          QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs)),
+      overall_connection_timeout_(QuicTime::Delta::Infinite()),
+      time_of_last_received_packet_(clock_->ApproximateNow()),
+      time_of_last_sent_new_packet_(clock_->ApproximateNow()),
+      sequence_number_of_last_sent_packet_(0),
+      sent_packet_manager_(
+          is_server, clock_, &stats_,
+          FLAGS_quic_use_bbr_congestion_control ? kBBR : kCubic,
+          FLAGS_quic_use_time_loss_detection ? kTime : kNack),
+      version_negotiation_state_(START_NEGOTIATION),
+      is_server_(is_server),
+      connected_(true),
+      peer_ip_changed_(false),
+      peer_port_changed_(false),
+      self_ip_changed_(false),
+      self_port_changed_(false) {
+  DVLOG(1) << ENDPOINT << "Created connection with connection_id: "
+           << connection_id;
+  if (!FLAGS_quic_unified_timeouts) {
+    timeout_alarm_->Set(clock_->ApproximateNow().Add(idle_network_timeout_));
+  }
+  framer_.set_visitor(this);
+  framer_.set_received_entropy_calculator(&received_packet_manager_);
+  stats_.connection_creation_time = clock_->ApproximateNow();
+  sent_packet_manager_.set_network_change_visitor(this);
+}
+
+QuicConnection::~QuicConnection() {
+  if (owns_writer_) {
+    delete writer_;
+  }
+  STLDeleteElements(&undecryptable_packets_);
+  STLDeleteValues(&group_map_);
+  for (QueuedPacketList::iterator it = queued_packets_.begin();
+       it != queued_packets_.end(); ++it) {
+    delete it->serialized_packet.retransmittable_frames;
+    delete it->serialized_packet.packet;
+  }
+}
+
+void QuicConnection::SetFromConfig(const QuicConfig& config) {
+  if (FLAGS_quic_unified_timeouts) {
+    if (config.negotiated()) {
+      SetNetworkTimeouts(QuicTime::Delta::Infinite(),
+                         config.IdleConnectionStateLifetime());
+    } else {
+      SetNetworkTimeouts(config.max_time_before_crypto_handshake(),
+                         config.max_idle_time_before_crypto_handshake());
+    }
+  } else {
+    SetIdleNetworkTimeout(config.IdleConnectionStateLifetime());
+  }
+  sent_packet_manager_.SetFromConfig(config);
+}
+
+bool QuicConnection::SelectMutualVersion(
+    const QuicVersionVector& available_versions) {
+  // Try to find the highest mutual version by iterating over supported
+  // versions, starting with the highest, and breaking out of the loop once we
+  // find a matching version in the provided available_versions vector.
+  const QuicVersionVector& supported_versions = framer_.supported_versions();
+  for (size_t i = 0; i < supported_versions.size(); ++i) {
+    const QuicVersion& version = supported_versions[i];
+    if (std::find(available_versions.begin(), available_versions.end(),
+                  version) != available_versions.end()) {
+      framer_.set_version(version);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void QuicConnection::OnError(QuicFramer* framer) {
+  // Packets that we cannot decrypt are dropped.
+  // TODO(rch): add stats to measure this.
+  if (!connected_ || framer->error() == QUIC_DECRYPTION_FAILURE) {
+    return;
+  }
+  SendConnectionCloseWithDetails(framer->error(), framer->detailed_error());
+}
+
+void QuicConnection::OnPacket() {
+  DCHECK(last_stream_frames_.empty() &&
+         last_ack_frames_.empty() &&
+         last_congestion_frames_.empty() &&
+         last_stop_waiting_frames_.empty() &&
+         last_rst_frames_.empty() &&
+         last_goaway_frames_.empty() &&
+         last_window_update_frames_.empty() &&
+         last_blocked_frames_.empty() &&
+         last_ping_frames_.empty() &&
+         last_close_frames_.empty());
+}
+
+void QuicConnection::OnPublicResetPacket(
+    const QuicPublicResetPacket& packet) {
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnPublicResetPacket(packet);
+  }
+  CloseConnection(QUIC_PUBLIC_RESET, true);
+
+  DVLOG(1) << ENDPOINT << "Connection " << connection_id()
+           << " closed via QUIC_PUBLIC_RESET from peer.";
+}
+
+bool QuicConnection::OnProtocolVersionMismatch(QuicVersion received_version) {
+  DVLOG(1) << ENDPOINT << "Received packet with mismatched version "
+           << received_version;
+  // TODO(satyamshekhar): Implement no server state in this mode.
+  if (!is_server_) {
+    LOG(DFATAL) << ENDPOINT << "Framer called OnProtocolVersionMismatch. "
+                << "Closing connection.";
+    CloseConnection(QUIC_INTERNAL_ERROR, false);
+    return false;
+  }
+  DCHECK_NE(version(), received_version);
+
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnProtocolVersionMismatch(received_version);
+  }
+
+  switch (version_negotiation_state_) {
+    case START_NEGOTIATION:
+      if (!framer_.IsSupportedVersion(received_version)) {
+        SendVersionNegotiationPacket();
+        version_negotiation_state_ = NEGOTIATION_IN_PROGRESS;
+        return false;
+      }
+      break;
+
+    case NEGOTIATION_IN_PROGRESS:
+      if (!framer_.IsSupportedVersion(received_version)) {
+        SendVersionNegotiationPacket();
+        return false;
+      }
+      break;
+
+    case NEGOTIATED_VERSION:
+      // Might be old packets that were sent by the client before the version
+      // was negotiated. Drop these.
+      return false;
+
+    default:
+      DCHECK(false);
+  }
+
+  version_negotiation_state_ = NEGOTIATED_VERSION;
+  visitor_->OnSuccessfulVersionNegotiation(received_version);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnSuccessfulVersionNegotiation(received_version);
+  }
+  DVLOG(1) << ENDPOINT << "version negotiated " << received_version;
+
+  // Store the new version.
+  framer_.set_version(received_version);
+
+  // TODO(satyamshekhar): Store the sequence number of this packet and close the
+  // connection if we ever received a packet with incorrect version and whose
+  // sequence number is greater.
+  return true;
+}
+
+// Handles version negotiation for client connection.
+void QuicConnection::OnVersionNegotiationPacket(
+    const QuicVersionNegotiationPacket& packet) {
+  if (is_server_) {
+    LOG(DFATAL) << ENDPOINT << "Framer parsed VersionNegotiationPacket."
+                << " Closing connection.";
+    CloseConnection(QUIC_INTERNAL_ERROR, false);
+    return;
+  }
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnVersionNegotiationPacket(packet);
+  }
+
+  if (version_negotiation_state_ != START_NEGOTIATION) {
+    // Possibly a duplicate version negotiation packet.
+    return;
+  }
+
+  if (std::find(packet.versions.begin(),
+                packet.versions.end(), version()) !=
+      packet.versions.end()) {
+    DLOG(WARNING) << ENDPOINT << "The server already supports our version. "
+                  << "It should have accepted our connection.";
+    // Just drop the connection.
+    CloseConnection(QUIC_INVALID_VERSION_NEGOTIATION_PACKET, false);
+    return;
+  }
+
+  if (!SelectMutualVersion(packet.versions)) {
+    SendConnectionCloseWithDetails(QUIC_INVALID_VERSION,
+                                   "no common version found");
+    return;
+  }
+
+  DVLOG(1) << ENDPOINT
+           << "Negotiated version: " << QuicVersionToString(version());
+  server_supported_versions_ = packet.versions;
+  version_negotiation_state_ = NEGOTIATION_IN_PROGRESS;
+  RetransmitUnackedPackets(ALL_UNACKED_RETRANSMISSION);
+}
+
+void QuicConnection::OnRevivedPacket() {
+}
+
+bool QuicConnection::OnUnauthenticatedPublicHeader(
+    const QuicPacketPublicHeader& header) {
+  return true;
+}
+
+bool QuicConnection::OnUnauthenticatedHeader(const QuicPacketHeader& header) {
+  return true;
+}
+
+void QuicConnection::OnDecryptedPacket(EncryptionLevel level) {
+  last_decrypted_packet_level_ = level;
+}
+
+bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) {
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnPacketHeader(header);
+  }
+
+  if (!ProcessValidatedPacket()) {
+    return false;
+  }
+
+  // Will be decrement below if we fall through to return true;
+  ++stats_.packets_dropped;
+
+  if (header.public_header.connection_id != connection_id_) {
+    DVLOG(1) << ENDPOINT << "Ignoring packet from unexpected ConnectionId: "
+             << header.public_header.connection_id << " instead of "
+             << connection_id_;
+    if (debug_visitor_.get() != nullptr) {
+      debug_visitor_->OnIncorrectConnectionId(
+          header.public_header.connection_id);
+    }
+    return false;
+  }
+
+  if (!Near(header.packet_sequence_number,
+            last_header_.packet_sequence_number)) {
+    DVLOG(1) << ENDPOINT << "Packet " << header.packet_sequence_number
+             << " out of bounds.  Discarding";
+    SendConnectionCloseWithDetails(QUIC_INVALID_PACKET_HEADER,
+                                   "Packet sequence number out of bounds");
+    return false;
+  }
+
+  // If this packet has already been seen, or that the sender
+  // has told us will not be retransmitted, then stop processing the packet.
+  if (!received_packet_manager_.IsAwaitingPacket(
+          header.packet_sequence_number)) {
+    DVLOG(1) << ENDPOINT << "Packet " << header.packet_sequence_number
+             << " no longer being waited for.  Discarding.";
+    if (debug_visitor_.get() != nullptr) {
+      debug_visitor_->OnDuplicatePacket(header.packet_sequence_number);
+    }
+    return false;
+  }
+
+  if (version_negotiation_state_ != NEGOTIATED_VERSION) {
+    if (is_server_) {
+      if (!header.public_header.version_flag) {
+        DLOG(WARNING) << ENDPOINT << "Packet " << header.packet_sequence_number
+                      << " without version flag before version negotiated.";
+        // Packets should have the version flag till version negotiation is
+        // done.
+        CloseConnection(QUIC_INVALID_VERSION, false);
+        return false;
+      } else {
+        DCHECK_EQ(1u, header.public_header.versions.size());
+        DCHECK_EQ(header.public_header.versions[0], version());
+        version_negotiation_state_ = NEGOTIATED_VERSION;
+        visitor_->OnSuccessfulVersionNegotiation(version());
+        if (debug_visitor_.get() != nullptr) {
+          debug_visitor_->OnSuccessfulVersionNegotiation(version());
+        }
+      }
+    } else {
+      DCHECK(!header.public_header.version_flag);
+      // If the client gets a packet without the version flag from the server
+      // it should stop sending version since the version negotiation is done.
+      packet_generator_.StopSendingVersion();
+      version_negotiation_state_ = NEGOTIATED_VERSION;
+      visitor_->OnSuccessfulVersionNegotiation(version());
+      if (debug_visitor_.get() != nullptr) {
+        debug_visitor_->OnSuccessfulVersionNegotiation(version());
+      }
+    }
+  }
+
+  DCHECK_EQ(NEGOTIATED_VERSION, version_negotiation_state_);
+
+  --stats_.packets_dropped;
+  DVLOG(1) << ENDPOINT << "Received packet header: " << header;
+  last_header_ = header;
+  DCHECK(connected_);
+  return true;
+}
+
+void QuicConnection::OnFecProtectedPayload(StringPiece payload) {
+  DCHECK_EQ(IN_FEC_GROUP, last_header_.is_in_fec_group);
+  DCHECK_NE(0u, last_header_.fec_group);
+  QuicFecGroup* group = GetFecGroup();
+  if (group != nullptr) {
+    group->Update(last_decrypted_packet_level_, last_header_, payload);
+  }
+}
+
+bool QuicConnection::OnStreamFrame(const QuicStreamFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnStreamFrame(frame);
+  }
+  if (frame.stream_id != kCryptoStreamId &&
+      last_decrypted_packet_level_ == ENCRYPTION_NONE) {
+    DLOG(WARNING) << ENDPOINT
+                  << "Received an unencrypted data frame: closing connection";
+    SendConnectionClose(QUIC_UNENCRYPTED_STREAM_DATA);
+    return false;
+  }
+  last_stream_frames_.push_back(frame);
+  return true;
+}
+
+bool QuicConnection::OnAckFrame(const QuicAckFrame& incoming_ack) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnAckFrame(incoming_ack);
+  }
+  DVLOG(1) << ENDPOINT << "OnAckFrame: " << incoming_ack;
+
+  if (last_header_.packet_sequence_number <= largest_seen_packet_with_ack_) {
+    DVLOG(1) << ENDPOINT << "Received an old ack frame: ignoring";
+    return true;
+  }
+
+  if (!ValidateAckFrame(incoming_ack)) {
+    SendConnectionClose(QUIC_INVALID_ACK_DATA);
+    return false;
+  }
+
+  last_ack_frames_.push_back(incoming_ack);
+  return connected_;
+}
+
+void QuicConnection::ProcessAckFrame(const QuicAckFrame& incoming_ack) {
+  largest_seen_packet_with_ack_ = last_header_.packet_sequence_number;
+  sent_packet_manager_.OnIncomingAck(incoming_ack,
+                                     time_of_last_received_packet_);
+  sent_entropy_manager_.ClearEntropyBefore(
+      sent_packet_manager_.least_packet_awaited_by_peer() - 1);
+  if (sent_packet_manager_.HasPendingRetransmissions()) {
+    WriteIfNotBlocked();
+  }
+
+  // Always reset the retransmission alarm when an ack comes in, since we now
+  // have a better estimate of the current rtt than when it was set.
+  QuicTime retransmission_time = sent_packet_manager_.GetRetransmissionTime();
+  retransmission_alarm_->Update(retransmission_time,
+                                QuicTime::Delta::FromMilliseconds(1));
+}
+
+void QuicConnection::ProcessStopWaitingFrame(
+    const QuicStopWaitingFrame& stop_waiting) {
+  largest_seen_packet_with_stop_waiting_ = last_header_.packet_sequence_number;
+  received_packet_manager_.UpdatePacketInformationSentByPeer(stop_waiting);
+  // Possibly close any FecGroups which are now irrelevant.
+  CloseFecGroupsBefore(stop_waiting.least_unacked + 1);
+}
+
+bool QuicConnection::OnCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& feedback) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnCongestionFeedbackFrame(feedback);
+  }
+  last_congestion_frames_.push_back(feedback);
+  return connected_;
+}
+
+bool QuicConnection::OnStopWaitingFrame(const QuicStopWaitingFrame& frame) {
+  DCHECK(connected_);
+
+  if (last_header_.packet_sequence_number <=
+      largest_seen_packet_with_stop_waiting_) {
+    DVLOG(1) << ENDPOINT << "Received an old stop waiting frame: ignoring";
+    return true;
+  }
+
+  if (!ValidateStopWaitingFrame(frame)) {
+    SendConnectionClose(QUIC_INVALID_STOP_WAITING_DATA);
+    return false;
+  }
+
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnStopWaitingFrame(frame);
+  }
+
+  last_stop_waiting_frames_.push_back(frame);
+  return connected_;
+}
+
+bool QuicConnection::OnPingFrame(const QuicPingFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnPingFrame(frame);
+  }
+  last_ping_frames_.push_back(frame);
+  return true;
+}
+
+bool QuicConnection::ValidateAckFrame(const QuicAckFrame& incoming_ack) {
+  if (incoming_ack.largest_observed > packet_generator_.sequence_number()) {
+    DLOG(ERROR) << ENDPOINT << "Peer's observed unsent packet:"
+                << incoming_ack.largest_observed << " vs "
+                << packet_generator_.sequence_number();
+    // We got an error for data we have not sent.  Error out.
+    return false;
+  }
+
+  if (incoming_ack.largest_observed < sent_packet_manager_.largest_observed()) {
+    DLOG(ERROR) << ENDPOINT << "Peer's largest_observed packet decreased:"
+                << incoming_ack.largest_observed << " vs "
+                << sent_packet_manager_.largest_observed();
+    // A new ack has a diminished largest_observed value.  Error out.
+    // If this was an old packet, we wouldn't even have checked.
+    return false;
+  }
+
+  if (!incoming_ack.missing_packets.empty() &&
+      *incoming_ack.missing_packets.rbegin() > incoming_ack.largest_observed) {
+    DLOG(ERROR) << ENDPOINT << "Peer sent missing packet: "
+                << *incoming_ack.missing_packets.rbegin()
+                << " which is greater than largest observed: "
+                << incoming_ack.largest_observed;
+    return false;
+  }
+
+  if (!incoming_ack.missing_packets.empty() &&
+      *incoming_ack.missing_packets.begin() <
+      sent_packet_manager_.least_packet_awaited_by_peer()) {
+    DLOG(ERROR) << ENDPOINT << "Peer sent missing packet: "
+                << *incoming_ack.missing_packets.begin()
+                << " which is smaller than least_packet_awaited_by_peer_: "
+                << sent_packet_manager_.least_packet_awaited_by_peer();
+    return false;
+  }
+
+  if (!sent_entropy_manager_.IsValidEntropy(
+          incoming_ack.largest_observed,
+          incoming_ack.missing_packets,
+          incoming_ack.entropy_hash)) {
+    DLOG(ERROR) << ENDPOINT << "Peer sent invalid entropy.";
+    return false;
+  }
+
+  for (SequenceNumberSet::const_iterator iter =
+           incoming_ack.revived_packets.begin();
+       iter != incoming_ack.revived_packets.end(); ++iter) {
+    if (!ContainsKey(incoming_ack.missing_packets, *iter)) {
+      DLOG(ERROR) << ENDPOINT
+                  << "Peer specified revived packet which was not missing.";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool QuicConnection::ValidateStopWaitingFrame(
+    const QuicStopWaitingFrame& stop_waiting) {
+  if (stop_waiting.least_unacked <
+      received_packet_manager_.peer_least_packet_awaiting_ack()) {
+    DLOG(ERROR) << ENDPOINT << "Peer's sent low least_unacked: "
+                << stop_waiting.least_unacked << " vs "
+                << received_packet_manager_.peer_least_packet_awaiting_ack();
+    // We never process old ack frames, so this number should only increase.
+    return false;
+  }
+
+  if (stop_waiting.least_unacked >
+      last_header_.packet_sequence_number) {
+    DLOG(ERROR) << ENDPOINT << "Peer sent least_unacked:"
+                << stop_waiting.least_unacked
+                << " greater than the enclosing packet sequence number:"
+                << last_header_.packet_sequence_number;
+    return false;
+  }
+
+  return true;
+}
+
+void QuicConnection::OnFecData(const QuicFecData& fec) {
+  DCHECK_EQ(IN_FEC_GROUP, last_header_.is_in_fec_group);
+  DCHECK_NE(0u, last_header_.fec_group);
+  QuicFecGroup* group = GetFecGroup();
+  if (group != nullptr) {
+    group->UpdateFec(last_decrypted_packet_level_,
+                     last_header_.packet_sequence_number, fec);
+  }
+}
+
+bool QuicConnection::OnRstStreamFrame(const QuicRstStreamFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnRstStreamFrame(frame);
+  }
+  DVLOG(1) << ENDPOINT << "Stream reset with error "
+           << QuicUtils::StreamErrorToString(frame.error_code);
+  last_rst_frames_.push_back(frame);
+  return connected_;
+}
+
+bool QuicConnection::OnConnectionCloseFrame(
+    const QuicConnectionCloseFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnConnectionCloseFrame(frame);
+  }
+  DVLOG(1) << ENDPOINT << "Connection " << connection_id()
+           << " closed with error "
+           << QuicUtils::ErrorToString(frame.error_code)
+           << " " << frame.error_details;
+  last_close_frames_.push_back(frame);
+  return connected_;
+}
+
+bool QuicConnection::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnGoAwayFrame(frame);
+  }
+  DVLOG(1) << ENDPOINT << "Go away received with error "
+           << QuicUtils::ErrorToString(frame.error_code)
+           << " and reason:" << frame.reason_phrase;
+  last_goaway_frames_.push_back(frame);
+  return connected_;
+}
+
+bool QuicConnection::OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnWindowUpdateFrame(frame);
+  }
+  DVLOG(1) << ENDPOINT << "WindowUpdate received for stream: "
+           << frame.stream_id << " with byte offset: " << frame.byte_offset;
+  last_window_update_frames_.push_back(frame);
+  return connected_;
+}
+
+bool QuicConnection::OnBlockedFrame(const QuicBlockedFrame& frame) {
+  DCHECK(connected_);
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnBlockedFrame(frame);
+  }
+  DVLOG(1) << ENDPOINT << "Blocked frame received for stream: "
+           << frame.stream_id;
+  last_blocked_frames_.push_back(frame);
+  return connected_;
+}
+
+void QuicConnection::OnPacketComplete() {
+  // Don't do anything if this packet closed the connection.
+  if (!connected_) {
+    ClearLastFrames();
+    return;
+  }
+
+  DVLOG(1) << ENDPOINT << (last_packet_revived_ ? "Revived" : "Got")
+           << " packet " << last_header_.packet_sequence_number
+           << " with " << last_stream_frames_.size()<< " stream frames "
+           << last_ack_frames_.size() << " acks, "
+           << last_congestion_frames_.size() << " congestions, "
+           << last_stop_waiting_frames_.size() << " stop_waiting, "
+           << last_rst_frames_.size() << " rsts, "
+           << last_goaway_frames_.size() << " goaways, "
+           << last_window_update_frames_.size() << " window updates, "
+           << last_blocked_frames_.size() << " blocked, "
+           << last_ping_frames_.size() << " pings, "
+           << last_close_frames_.size() << " closes, "
+           << "for " << last_header_.public_header.connection_id;
+
+  ++num_packets_received_since_last_ack_sent_;
+
+  // Call MaybeQueueAck() before recording the received packet, since we want
+  // to trigger an ack if the newly received packet was previously missing.
+  MaybeQueueAck();
+
+  // Record received or revived packet to populate ack info correctly before
+  // processing stream frames, since the processing may result in a response
+  // packet with a bundled ack.
+  if (last_packet_revived_) {
+    received_packet_manager_.RecordPacketRevived(
+        last_header_.packet_sequence_number);
+  } else {
+    received_packet_manager_.RecordPacketReceived(
+        last_size_, last_header_, time_of_last_received_packet_);
+  }
+
+  if (!last_stream_frames_.empty()) {
+    visitor_->OnStreamFrames(last_stream_frames_);
+  }
+
+  for (size_t i = 0; i < last_stream_frames_.size(); ++i) {
+    stats_.stream_bytes_received +=
+        last_stream_frames_[i].data.TotalBufferSize();
+  }
+
+  // Process window updates, blocked, stream resets, acks, then congestion
+  // feedback.
+  if (!last_window_update_frames_.empty()) {
+    visitor_->OnWindowUpdateFrames(last_window_update_frames_);
+  }
+  if (!last_blocked_frames_.empty()) {
+    visitor_->OnBlockedFrames(last_blocked_frames_);
+  }
+  for (size_t i = 0; i < last_goaway_frames_.size(); ++i) {
+    visitor_->OnGoAway(last_goaway_frames_[i]);
+  }
+  for (size_t i = 0; i < last_rst_frames_.size(); ++i) {
+    visitor_->OnRstStream(last_rst_frames_[i]);
+  }
+  for (size_t i = 0; i < last_ack_frames_.size(); ++i) {
+    ProcessAckFrame(last_ack_frames_[i]);
+  }
+  for (size_t i = 0; i < last_congestion_frames_.size(); ++i) {
+    sent_packet_manager_.OnIncomingQuicCongestionFeedbackFrame(
+        last_congestion_frames_[i], time_of_last_received_packet_);
+  }
+  for (size_t i = 0; i < last_stop_waiting_frames_.size(); ++i) {
+    ProcessStopWaitingFrame(last_stop_waiting_frames_[i]);
+  }
+  if (!last_close_frames_.empty()) {
+    CloseConnection(last_close_frames_[0].error_code, true);
+    DCHECK(!connected_);
+  }
+
+  // If there are new missing packets to report, send an ack immediately.
+  if (received_packet_manager_.HasNewMissingPackets()) {
+    ack_queued_ = true;
+    ack_alarm_->Cancel();
+  }
+
+  UpdateStopWaitingCount();
+
+  ClearLastFrames();
+}
+
+void QuicConnection::MaybeQueueAck() {
+  // If the incoming packet was missing, send an ack immediately.
+  ack_queued_ = received_packet_manager_.IsMissing(
+      last_header_.packet_sequence_number);
+
+  if (!ack_queued_ && ShouldLastPacketInstigateAck()) {
+    if (ack_alarm_->IsSet()) {
+      ack_queued_ = true;
+    } else {
+      // Send an ack much more quickly for crypto handshake packets.
+      QuicTime::Delta delayed_ack_time = sent_packet_manager_.DelayedAckTime();
+      if (last_stream_frames_.size() == 1 &&
+          last_stream_frames_[0].stream_id == kCryptoStreamId) {
+        delayed_ack_time = QuicTime::Delta::Zero();
+      }
+      ack_alarm_->Set(clock_->ApproximateNow().Add(delayed_ack_time));
+      DVLOG(1) << "Ack timer set; next packet or timer will trigger ACK.";
+    }
+  }
+
+  if (ack_queued_) {
+    ack_alarm_->Cancel();
+  }
+}
+
+void QuicConnection::ClearLastFrames() {
+  last_stream_frames_.clear();
+  last_ack_frames_.clear();
+  last_congestion_frames_.clear();
+  last_stop_waiting_frames_.clear();
+  last_rst_frames_.clear();
+  last_goaway_frames_.clear();
+  last_window_update_frames_.clear();
+  last_blocked_frames_.clear();
+  last_ping_frames_.clear();
+  last_close_frames_.clear();
+}
+
+QuicAckFrame* QuicConnection::CreateAckFrame() {
+  QuicAckFrame* outgoing_ack = new QuicAckFrame();
+  received_packet_manager_.UpdateReceivedPacketInfo(
+      outgoing_ack, clock_->ApproximateNow());
+  DVLOG(1) << ENDPOINT << "Creating ack frame: " << *outgoing_ack;
+  return outgoing_ack;
+}
+
+QuicCongestionFeedbackFrame* QuicConnection::CreateFeedbackFrame() {
+  return new QuicCongestionFeedbackFrame(outgoing_congestion_feedback_);
+}
+
+QuicStopWaitingFrame* QuicConnection::CreateStopWaitingFrame() {
+  QuicStopWaitingFrame stop_waiting;
+  UpdateStopWaiting(&stop_waiting);
+  return new QuicStopWaitingFrame(stop_waiting);
+}
+
+bool QuicConnection::ShouldLastPacketInstigateAck() const {
+  if (!last_stream_frames_.empty() ||
+      !last_goaway_frames_.empty() ||
+      !last_rst_frames_.empty() ||
+      !last_window_update_frames_.empty() ||
+      !last_blocked_frames_.empty() ||
+      !last_ping_frames_.empty()) {
+    return true;
+  }
+
+  if (!last_ack_frames_.empty() && last_ack_frames_.back().is_truncated) {
+    return true;
+  }
+  // Always send an ack every 20 packets in order to allow the peer to discard
+  // information from the SentPacketManager and provide an RTT measurement.
+  if (num_packets_received_since_last_ack_sent_ >=
+          kMaxPacketsReceivedBeforeAckSend) {
+    return true;
+  }
+  return false;
+}
+
+void QuicConnection::UpdateStopWaitingCount() {
+  if (last_ack_frames_.empty()) {
+    return;
+  }
+
+  // If the peer is still waiting for a packet that we are no longer planning to
+  // send, send an ack to raise the high water mark.
+  if (!last_ack_frames_.back().missing_packets.empty() &&
+      GetLeastUnacked() > *last_ack_frames_.back().missing_packets.begin()) {
+    ++stop_waiting_count_;
+  } else {
+    stop_waiting_count_ = 0;
+  }
+}
+
+QuicPacketSequenceNumber QuicConnection::GetLeastUnacked() const {
+  return sent_packet_manager_.GetLeastUnacked();
+}
+
+void QuicConnection::MaybeSendInResponseToPacket() {
+  if (!connected_) {
+    return;
+  }
+  ScopedPacketBundler bundler(this, ack_queued_ ? SEND_ACK : NO_ACK);
+
+  // Now that we have received an ack, we might be able to send packets which
+  // are queued locally, or drain streams which are blocked.
+  if (CanWrite(HAS_RETRANSMITTABLE_DATA)) {
+    OnCanWrite();
+  }
+}
+
+void QuicConnection::SendVersionNegotiationPacket() {
+  // TODO(alyssar): implement zero server state negotiation.
+  pending_version_negotiation_packet_ = true;
+  if (writer_->IsWriteBlocked()) {
+    visitor_->OnWriteBlocked();
+    return;
+  }
+  DVLOG(1) << ENDPOINT << "Sending version negotiation packet: {"
+           << QuicVersionVectorToString(framer_.supported_versions()) << "}";
+  scoped_ptr<QuicEncryptedPacket> version_packet(
+      packet_generator_.SerializeVersionNegotiationPacket(
+          framer_.supported_versions()));
+  WriteResult result = writer_->WritePacket(
+      version_packet->data(), version_packet->length(),
+      self_address().address(), peer_address());
+
+  if (result.status == WRITE_STATUS_ERROR) {
+    // We can't send an error as the socket is presumably borked.
+    CloseConnection(QUIC_PACKET_WRITE_ERROR, false);
+    return;
+  }
+  if (result.status == WRITE_STATUS_BLOCKED) {
+    visitor_->OnWriteBlocked();
+    if (writer_->IsWriteBlockedDataBuffered()) {
+      pending_version_negotiation_packet_ = false;
+    }
+    return;
+  }
+
+  pending_version_negotiation_packet_ = false;
+}
+
+QuicConsumedData QuicConnection::SendStreamData(
+    QuicStreamId id,
+    const IOVector& data,
+    QuicStreamOffset offset,
+    bool fin,
+    FecProtection fec_protection,
+    QuicAckNotifier::DelegateInterface* delegate) {
+  if (!fin && data.Empty()) {
+    LOG(DFATAL) << "Attempt to send empty stream frame";
+  }
+
+  // This notifier will be owned by the AckNotifierManager (or deleted below if
+  // no data or FIN was consumed).
+  QuicAckNotifier* notifier = nullptr;
+  if (delegate) {
+    notifier = new QuicAckNotifier(delegate);
+  }
+
+  // Opportunistically bundle an ack with every outgoing packet.
+  // Particularly, we want to bundle with handshake packets since we don't know
+  // which decrypter will be used on an ack packet following a handshake
+  // packet (a handshake packet from client to server could result in a REJ or a
+  // SHLO from the server, leading to two different decrypters at the server.)
+  //
+  // TODO(jri): Note that ConsumeData may cause a response packet to be sent.
+  // We may end up sending stale ack information if there are undecryptable
+  // packets hanging around and/or there are revivable packets which may get
+  // handled after this packet is sent. Change ScopedPacketBundler to do the
+  // right thing: check ack_queued_, and then check undecryptable packets and
+  // also if there is possibility of revival. Only bundle an ack if there's no
+  // processing left that may cause received_info_ to change.
+  ScopedPacketBundler ack_bundler(this, BUNDLE_PENDING_ACK);
+  QuicConsumedData consumed_data =
+      packet_generator_.ConsumeData(id, data, offset, fin, fec_protection,
+                                    notifier);
+
+  if (notifier &&
+      (consumed_data.bytes_consumed == 0 && !consumed_data.fin_consumed)) {
+    // No data was consumed, nor was a fin consumed, so delete the notifier.
+    delete notifier;
+  }
+
+  return consumed_data;
+}
+
+void QuicConnection::SendRstStream(QuicStreamId id,
+                                   QuicRstStreamErrorCode error,
+                                   QuicStreamOffset bytes_written) {
+  // Opportunistically bundle an ack with this outgoing packet.
+  ScopedPacketBundler ack_bundler(this, BUNDLE_PENDING_ACK);
+  packet_generator_.AddControlFrame(QuicFrame(new QuicRstStreamFrame(
+      id, AdjustErrorForVersion(error, version()), bytes_written)));
+}
+
+void QuicConnection::SendWindowUpdate(QuicStreamId id,
+                                      QuicStreamOffset byte_offset) {
+  // Opportunistically bundle an ack with this outgoing packet.
+  ScopedPacketBundler ack_bundler(this, BUNDLE_PENDING_ACK);
+  packet_generator_.AddControlFrame(
+      QuicFrame(new QuicWindowUpdateFrame(id, byte_offset)));
+}
+
+void QuicConnection::SendBlocked(QuicStreamId id) {
+  // Opportunistically bundle an ack with this outgoing packet.
+  ScopedPacketBundler ack_bundler(this, BUNDLE_PENDING_ACK);
+  packet_generator_.AddControlFrame(QuicFrame(new QuicBlockedFrame(id)));
+}
+
+const QuicConnectionStats& QuicConnection::GetStats() {
+  // Update rtt and estimated bandwidth.
+  stats_.min_rtt_us =
+      sent_packet_manager_.GetRttStats()->min_rtt().ToMicroseconds();
+  stats_.srtt_us =
+      sent_packet_manager_.GetRttStats()->SmoothedRtt().ToMicroseconds();
+  stats_.estimated_bandwidth =
+      sent_packet_manager_.BandwidthEstimate().ToBytesPerSecond();
+  stats_.congestion_window = sent_packet_manager_.GetCongestionWindow();
+  stats_.slow_start_threshold = sent_packet_manager_.GetSlowStartThreshold();
+  stats_.max_packet_size = packet_generator_.max_packet_length();
+  return stats_;
+}
+
+void QuicConnection::ProcessUdpPacket(const IPEndPoint& self_address,
+                                      const IPEndPoint& peer_address,
+                                      const QuicEncryptedPacket& packet) {
+  if (!connected_) {
+    return;
+  }
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnPacketReceived(self_address, peer_address, packet);
+  }
+  last_packet_revived_ = false;
+  last_size_ = packet.length();
+
+  CheckForAddressMigration(self_address, peer_address);
+
+  stats_.bytes_received += packet.length();
+  ++stats_.packets_received;
+
+  if (!framer_.ProcessPacket(packet)) {
+    // If we are unable to decrypt this packet, it might be
+    // because the CHLO or SHLO packet was lost.
+    if (framer_.error() == QUIC_DECRYPTION_FAILURE) {
+      if (encryption_level_ != ENCRYPTION_FORWARD_SECURE &&
+          undecryptable_packets_.size() < kMaxUndecryptablePackets) {
+        QueueUndecryptablePacket(packet);
+      } else if (debug_visitor_.get() != nullptr) {
+        debug_visitor_->OnUndecryptablePacket();
+      }
+    }
+    DVLOG(1) << ENDPOINT << "Unable to process packet.  Last packet processed: "
+             << last_header_.packet_sequence_number;
+    return;
+  }
+
+  ++stats_.packets_processed;
+  MaybeProcessUndecryptablePackets();
+  MaybeProcessRevivedPacket();
+  MaybeSendInResponseToPacket();
+  SetPingAlarm();
+}
+
+void QuicConnection::CheckForAddressMigration(
+    const IPEndPoint& self_address, const IPEndPoint& peer_address) {
+  peer_ip_changed_ = false;
+  peer_port_changed_ = false;
+  self_ip_changed_ = false;
+  self_port_changed_ = false;
+
+  if (peer_address_.address().empty()) {
+    peer_address_ = peer_address;
+  }
+  if (self_address_.address().empty()) {
+    self_address_ = self_address;
+  }
+
+  if (!peer_address.address().empty() && !peer_address_.address().empty()) {
+    peer_ip_changed_ = (peer_address.address() != peer_address_.address());
+    peer_port_changed_ = (peer_address.port() != peer_address_.port());
+
+    // Store in case we want to migrate connection in ProcessValidatedPacket.
+    migrating_peer_port_ = peer_address.port();
+  }
+
+  if (!self_address.address().empty() && !self_address_.address().empty()) {
+    self_ip_changed_ = (self_address.address() != self_address_.address());
+    self_port_changed_ = (self_address.port() != self_address_.port());
+  }
+}
+
+void QuicConnection::OnCanWrite() {
+  DCHECK(!writer_->IsWriteBlocked());
+
+  WriteQueuedPackets();
+  WritePendingRetransmissions();
+
+  // Sending queued packets may have caused the socket to become write blocked,
+  // or the congestion manager to prohibit sending.  If we've sent everything
+  // we had queued and we're still not blocked, let the visitor know it can
+  // write more.
+  if (!CanWrite(HAS_RETRANSMITTABLE_DATA)) {
+    return;
+  }
+
+  {  // Limit the scope of the bundler.
+    // Set |include_ack| to false in bundler; ack inclusion happens elsewhere.
+    ScopedPacketBundler bundler(this, NO_ACK);
+    visitor_->OnCanWrite();
+  }
+
+  // After the visitor writes, it may have caused the socket to become write
+  // blocked or the congestion manager to prohibit sending, so check again.
+  if (visitor_->WillingAndAbleToWrite() &&
+      !resume_writes_alarm_->IsSet() &&
+      CanWrite(HAS_RETRANSMITTABLE_DATA)) {
+    // We're not write blocked, but some stream didn't write out all of its
+    // bytes. Register for 'immediate' resumption so we'll keep writing after
+    // other connections and events have had a chance to use the thread.
+    resume_writes_alarm_->Set(clock_->ApproximateNow());
+  }
+}
+
+void QuicConnection::WriteIfNotBlocked() {
+  if (!writer_->IsWriteBlocked()) {
+    OnCanWrite();
+  }
+}
+
+bool QuicConnection::ProcessValidatedPacket() {
+  if (peer_ip_changed_ || self_ip_changed_ || self_port_changed_) {
+    SendConnectionCloseWithDetails(
+        QUIC_ERROR_MIGRATING_ADDRESS,
+        "Neither IP address migration, nor self port migration are supported.");
+    return false;
+  }
+
+  // Peer port migration is supported, do it now if port has changed.
+  if (peer_port_changed_) {
+    DVLOG(1) << ENDPOINT << "Peer's port changed from "
+             << peer_address_.port() << " to " << migrating_peer_port_
+             << ", migrating connection.";
+    peer_address_ = IPEndPoint(peer_address_.address(), migrating_peer_port_);
+  }
+
+  time_of_last_received_packet_ = clock_->Now();
+  DVLOG(1) << ENDPOINT << "time of last received packet: "
+           << time_of_last_received_packet_.ToDebuggingValue();
+
+  if (is_server_ && encryption_level_ == ENCRYPTION_NONE &&
+      last_size_ > packet_generator_.max_packet_length()) {
+    packet_generator_.set_max_packet_length(last_size_);
+  }
+  return true;
+}
+
+void QuicConnection::WriteQueuedPackets() {
+  DCHECK(!writer_->IsWriteBlocked());
+
+  if (pending_version_negotiation_packet_) {
+    SendVersionNegotiationPacket();
+  }
+
+  QueuedPacketList::iterator packet_iterator = queued_packets_.begin();
+  while (packet_iterator != queued_packets_.end() &&
+         WritePacket(&(*packet_iterator))) {
+    packet_iterator = queued_packets_.erase(packet_iterator);
+  }
+}
+
+void QuicConnection::WritePendingRetransmissions() {
+  // Keep writing as long as there's a pending retransmission which can be
+  // written.
+  while (sent_packet_manager_.HasPendingRetransmissions()) {
+    const QuicSentPacketManager::PendingRetransmission pending =
+        sent_packet_manager_.NextPendingRetransmission();
+    if (!CanWrite(HAS_RETRANSMITTABLE_DATA)) {
+      break;
+    }
+
+    // Re-packetize the frames with a new sequence number for retransmission.
+    // Retransmitted data packets do not use FEC, even when it's enabled.
+    // Retransmitted packets use the same sequence number length as the
+    // original.
+    // Flush the packet generator before making a new packet.
+    // TODO(ianswett): Implement ReserializeAllFrames as a separate path that
+    // does not require the creator to be flushed.
+    packet_generator_.FlushAllQueuedFrames();
+    SerializedPacket serialized_packet = packet_generator_.ReserializeAllFrames(
+        pending.retransmittable_frames.frames(),
+        pending.sequence_number_length);
+
+    DVLOG(1) << ENDPOINT << "Retransmitting " << pending.sequence_number
+             << " as " << serialized_packet.sequence_number;
+    SendOrQueuePacket(
+        QueuedPacket(serialized_packet,
+                     pending.retransmittable_frames.encryption_level(),
+                     pending.transmission_type,
+                     pending.sequence_number));
+  }
+}
+
+void QuicConnection::RetransmitUnackedPackets(
+    TransmissionType retransmission_type) {
+  sent_packet_manager_.RetransmitUnackedPackets(retransmission_type);
+
+  WriteIfNotBlocked();
+}
+
+void QuicConnection::NeuterUnencryptedPackets() {
+  sent_packet_manager_.NeuterUnencryptedPackets();
+  // This may have changed the retransmission timer, so re-arm it.
+  QuicTime retransmission_time = sent_packet_manager_.GetRetransmissionTime();
+  retransmission_alarm_->Update(retransmission_time,
+                                QuicTime::Delta::FromMilliseconds(1));
+}
+
+bool QuicConnection::ShouldGeneratePacket(
+    TransmissionType transmission_type,
+    HasRetransmittableData retransmittable,
+    IsHandshake handshake) {
+  // We should serialize handshake packets immediately to ensure that they
+  // end up sent at the right encryption level.
+  if (handshake == IS_HANDSHAKE) {
+    return true;
+  }
+
+  return CanWrite(retransmittable);
+}
+
+bool QuicConnection::CanWrite(HasRetransmittableData retransmittable) {
+  if (!connected_) {
+    return false;
+  }
+
+  if (writer_->IsWriteBlocked()) {
+    visitor_->OnWriteBlocked();
+    return false;
+  }
+
+  QuicTime now = clock_->Now();
+  QuicTime::Delta delay = sent_packet_manager_.TimeUntilSend(
+      now, retransmittable);
+  if (delay.IsInfinite()) {
+    send_alarm_->Cancel();
+    return false;
+  }
+
+  // If the scheduler requires a delay, then we can not send this packet now.
+  if (!delay.IsZero()) {
+    send_alarm_->Update(now.Add(delay), QuicTime::Delta::FromMilliseconds(1));
+    DVLOG(1) << "Delaying sending.";
+    return false;
+  }
+  send_alarm_->Cancel();
+  return true;
+}
+
+bool QuicConnection::WritePacket(QueuedPacket* packet) {
+  if (!WritePacketInner(packet)) {
+    return false;
+  }
+  delete packet->serialized_packet.retransmittable_frames;
+  delete packet->serialized_packet.packet;
+  packet->serialized_packet.retransmittable_frames = nullptr;
+  packet->serialized_packet.packet = nullptr;
+  return true;
+}
+
+bool QuicConnection::WritePacketInner(QueuedPacket* packet) {
+  if (ShouldDiscardPacket(*packet)) {
+    ++stats_.packets_discarded;
+    return true;
+  }
+  // Connection close packets are encrypted and saved, so don't exit early.
+  if (writer_->IsWriteBlocked() && !IsConnectionClose(*packet)) {
+    return false;
+  }
+
+  QuicPacketSequenceNumber sequence_number =
+      packet->serialized_packet.sequence_number;
+  DCHECK_LE(sequence_number_of_last_sent_packet_, sequence_number);
+  sequence_number_of_last_sent_packet_ = sequence_number;
+
+  QuicEncryptedPacket* encrypted = framer_.EncryptPacket(
+      packet->encryption_level,
+      sequence_number,
+      *packet->serialized_packet.packet);
+  if (encrypted == nullptr) {
+    LOG(DFATAL) << ENDPOINT << "Failed to encrypt packet number "
+                << sequence_number;
+    // CloseConnection does not send close packet, so no infinite loop here.
+    CloseConnection(QUIC_ENCRYPTION_FAILURE, false);
+    return false;
+  }
+
+  // Connection close packets are eventually owned by TimeWaitListManager.
+  // Others are deleted at the end of this call.
+  scoped_ptr<QuicEncryptedPacket> encrypted_deleter;
+  if (IsConnectionClose(*packet)) {
+    DCHECK(connection_close_packet_.get() == nullptr);
+    connection_close_packet_.reset(encrypted);
+    // This assures we won't try to write *forced* packets when blocked.
+    // Return true to stop processing.
+    if (writer_->IsWriteBlocked()) {
+      visitor_->OnWriteBlocked();
+      return true;
+    }
+  } else {
+    encrypted_deleter.reset(encrypted);
+  }
+
+  if (!FLAGS_quic_allow_oversized_packets_for_test) {
+    DCHECK_LE(encrypted->length(), kMaxPacketSize);
+  }
+  DCHECK_LE(encrypted->length(), packet_generator_.max_packet_length());
+  DVLOG(1) << ENDPOINT << "Sending packet " << sequence_number << " : "
+           << (packet->serialized_packet.packet->is_fec_packet() ? "FEC " :
+               (IsRetransmittable(*packet) == HAS_RETRANSMITTABLE_DATA
+                ? "data bearing " : " ack only "))
+           << ", encryption level: "
+           << QuicUtils::EncryptionLevelToString(packet->encryption_level)
+           << ", length:"
+           << packet->serialized_packet.packet->length()
+           << ", encrypted length:"
+           << encrypted->length();
+  DVLOG(2) << ENDPOINT << "packet(" << sequence_number << "): " << std::endl
+           << QuicUtils::StringToHexASCIIDump(
+               packet->serialized_packet.packet->AsStringPiece());
+
+  WriteResult result = writer_->WritePacket(encrypted->data(),
+                                            encrypted->length(),
+                                            self_address().address(),
+                                            peer_address());
+  if (result.error_code == ERR_IO_PENDING) {
+    DCHECK_EQ(WRITE_STATUS_BLOCKED, result.status);
+  }
+  if (debug_visitor_.get() != nullptr) {
+    // Pass the write result to the visitor.
+    debug_visitor_->OnPacketSent(sequence_number,
+                                 packet->original_sequence_number,
+                                 packet->encryption_level,
+                                 packet->transmission_type,
+                                 *encrypted,
+                                 result);
+  }
+
+  if (result.status == WRITE_STATUS_BLOCKED) {
+    visitor_->OnWriteBlocked();
+    // If the socket buffers the the data, then the packet should not
+    // be queued and sent again, which would result in an unnecessary
+    // duplicate packet being sent.  The helper must call OnCanWrite
+    // when the write completes, and OnWriteError if an error occurs.
+    if (!writer_->IsWriteBlockedDataBuffered()) {
+      return false;
+    }
+  }
+  QuicTime now = clock_->Now();
+  if (packet->transmission_type == NOT_RETRANSMISSION) {
+    time_of_last_sent_new_packet_ = now;
+  }
+  SetPingAlarm();
+  DVLOG(1) << ENDPOINT << "time of last sent packet: "
+           << now.ToDebuggingValue();
+
+  // TODO(ianswett): Change the sequence number length and other packet creator
+  // options by a more explicit API than setting a struct value directly,
+  // perhaps via the NetworkChangeVisitor.
+  packet_generator_.UpdateSequenceNumberLength(
+      sent_packet_manager_.least_packet_awaited_by_peer(),
+      sent_packet_manager_.GetCongestionWindow());
+
+  bool reset_retransmission_alarm = sent_packet_manager_.OnPacketSent(
+      &packet->serialized_packet,
+      packet->original_sequence_number,
+      now,
+      encrypted->length(),
+      packet->transmission_type,
+      IsRetransmittable(*packet));
+
+  if (reset_retransmission_alarm || !retransmission_alarm_->IsSet()) {
+    retransmission_alarm_->Update(sent_packet_manager_.GetRetransmissionTime(),
+                                  QuicTime::Delta::FromMilliseconds(1));
+  }
+
+  stats_.bytes_sent += result.bytes_written;
+  ++stats_.packets_sent;
+  if (packet->transmission_type != NOT_RETRANSMISSION) {
+    stats_.bytes_retransmitted += result.bytes_written;
+    ++stats_.packets_retransmitted;
+  }
+
+  if (result.status == WRITE_STATUS_ERROR) {
+    OnWriteError(result.error_code);
+    return false;
+  }
+
+  return true;
+}
+
+bool QuicConnection::ShouldDiscardPacket(const QueuedPacket& packet) {
+  if (!connected_) {
+    DVLOG(1) << ENDPOINT
+             << "Not sending packet as connection is disconnected.";
+    return true;
+  }
+
+  QuicPacketSequenceNumber sequence_number =
+      packet.serialized_packet.sequence_number;
+  if (encryption_level_ == ENCRYPTION_FORWARD_SECURE &&
+      packet.encryption_level == ENCRYPTION_NONE) {
+    // Drop packets that are NULL encrypted since the peer won't accept them
+    // anymore.
+    DVLOG(1) << ENDPOINT << "Dropping NULL encrypted packet: "
+             << sequence_number << " since the connection is forward secure.";
+    return true;
+  }
+
+  // If a retransmission has been acked before sending, don't send it.
+  // This occurs if a packet gets serialized, queued, then discarded.
+  if (packet.transmission_type != NOT_RETRANSMISSION &&
+      (!sent_packet_manager_.IsUnacked(packet.original_sequence_number) ||
+       !sent_packet_manager_.HasRetransmittableFrames(
+           packet.original_sequence_number))) {
+    DVLOG(1) << ENDPOINT << "Dropping unacked packet: " << sequence_number
+             << " A previous transmission was acked while write blocked.";
+    return true;
+  }
+
+  return false;
+}
+
+void QuicConnection::OnWriteError(int error_code) {
+  DVLOG(1) << ENDPOINT << "Write failed with error: " << error_code
+           << " (" << ErrorToString(error_code) << ")";
+  // We can't send an error as the socket is presumably borked.
+  CloseConnection(QUIC_PACKET_WRITE_ERROR, false);
+}
+
+void QuicConnection::OnSerializedPacket(
+    const SerializedPacket& serialized_packet) {
+  if (serialized_packet.retransmittable_frames) {
+    serialized_packet.retransmittable_frames->
+        set_encryption_level(encryption_level_);
+  }
+  SendOrQueuePacket(QueuedPacket(serialized_packet, encryption_level_));
+}
+
+void QuicConnection::OnCongestionWindowChange(QuicByteCount congestion_window) {
+  packet_generator_.OnCongestionWindowChange(congestion_window);
+  visitor_->OnCongestionWindowChange(clock_->ApproximateNow());
+}
+
+void QuicConnection::OnHandshakeComplete() {
+  sent_packet_manager_.SetHandshakeConfirmed();
+}
+
+void QuicConnection::SendOrQueuePacket(QueuedPacket packet) {
+  // The caller of this function is responsible for checking CanWrite().
+  if (packet.serialized_packet.packet == nullptr) {
+    LOG(DFATAL)
+        << "packet.serialized_packet.packet == nullptr in to SendOrQueuePacket";
+    return;
+  }
+
+  sent_entropy_manager_.RecordPacketEntropyHash(
+      packet.serialized_packet.sequence_number,
+      packet.serialized_packet.entropy_hash);
+  LOG_IF(DFATAL, !queued_packets_.empty() && !writer_->IsWriteBlocked())
+      << "Packets should only be left queued if we're write blocked.";
+  if (!WritePacket(&packet)) {
+    queued_packets_.push_back(packet);
+  }
+}
+
+void QuicConnection::UpdateStopWaiting(QuicStopWaitingFrame* stop_waiting) {
+  stop_waiting->least_unacked = GetLeastUnacked();
+  stop_waiting->entropy_hash = sent_entropy_manager_.GetCumulativeEntropy(
+      stop_waiting->least_unacked - 1);
+}
+
+void QuicConnection::SendPing() {
+  if (retransmission_alarm_->IsSet()) {
+    return;
+  }
+  packet_generator_.AddControlFrame(QuicFrame(new QuicPingFrame));
+}
+
+void QuicConnection::SendAck() {
+  ack_alarm_->Cancel();
+  stop_waiting_count_ = 0;
+  num_packets_received_since_last_ack_sent_ = 0;
+  bool send_feedback = false;
+
+  // Deprecating the Congestion Feedback Frame after QUIC_VERSION_22.
+  if (version() <= QUIC_VERSION_22) {
+    if (received_packet_manager_.GenerateCongestionFeedback(
+            &outgoing_congestion_feedback_)) {
+      DVLOG(1) << ENDPOINT << "Sending feedback: "
+               << outgoing_congestion_feedback_;
+      send_feedback = true;
+    }
+  }
+
+  packet_generator_.SetShouldSendAck(send_feedback, true);
+}
+
+void QuicConnection::OnRetransmissionTimeout() {
+  if (!sent_packet_manager_.HasUnackedPackets()) {
+    return;
+  }
+
+  sent_packet_manager_.OnRetransmissionTimeout();
+  WriteIfNotBlocked();
+
+  // A write failure can result in the connection being closed, don't attempt to
+  // write further packets, or to set alarms.
+  if (!connected_) {
+    return;
+  }
+
+  // In the TLP case, the SentPacketManager gives the connection the opportunity
+  // to send new data before retransmitting.
+  if (sent_packet_manager_.MaybeRetransmitTailLossProbe()) {
+    // Send the pending retransmission now that it's been queued.
+    WriteIfNotBlocked();
+  }
+
+  // Ensure the retransmission alarm is always set if there are unacked packets
+  // and nothing waiting to be sent.
+  if (!HasQueuedData() && !retransmission_alarm_->IsSet()) {
+    QuicTime rto_timeout = sent_packet_manager_.GetRetransmissionTime();
+    if (rto_timeout.IsInitialized()) {
+      retransmission_alarm_->Set(rto_timeout);
+    }
+  }
+}
+
+void QuicConnection::SetEncrypter(EncryptionLevel level,
+                                  QuicEncrypter* encrypter) {
+  framer_.SetEncrypter(level, encrypter);
+}
+
+const QuicEncrypter* QuicConnection::encrypter(EncryptionLevel level) const {
+  return framer_.encrypter(level);
+}
+
+void QuicConnection::SetDefaultEncryptionLevel(EncryptionLevel level) {
+  encryption_level_ = level;
+  packet_generator_.set_encryption_level(level);
+}
+
+void QuicConnection::SetDecrypter(QuicDecrypter* decrypter,
+                                  EncryptionLevel level) {
+  framer_.SetDecrypter(decrypter, level);
+}
+
+void QuicConnection::SetAlternativeDecrypter(QuicDecrypter* decrypter,
+                                             EncryptionLevel level,
+                                             bool latch_once_used) {
+  framer_.SetAlternativeDecrypter(decrypter, level, latch_once_used);
+}
+
+const QuicDecrypter* QuicConnection::decrypter() const {
+  return framer_.decrypter();
+}
+
+const QuicDecrypter* QuicConnection::alternative_decrypter() const {
+  return framer_.alternative_decrypter();
+}
+
+void QuicConnection::QueueUndecryptablePacket(
+    const QuicEncryptedPacket& packet) {
+  DVLOG(1) << ENDPOINT << "Queueing undecryptable packet.";
+  undecryptable_packets_.push_back(packet.Clone());
+}
+
+void QuicConnection::MaybeProcessUndecryptablePackets() {
+  if (undecryptable_packets_.empty() || encryption_level_ == ENCRYPTION_NONE) {
+    return;
+  }
+
+  while (connected_ && !undecryptable_packets_.empty()) {
+    DVLOG(1) << ENDPOINT << "Attempting to process undecryptable packet";
+    QuicEncryptedPacket* packet = undecryptable_packets_.front();
+    if (!framer_.ProcessPacket(*packet) &&
+        framer_.error() == QUIC_DECRYPTION_FAILURE) {
+      DVLOG(1) << ENDPOINT << "Unable to process undecryptable packet...";
+      break;
+    }
+    DVLOG(1) << ENDPOINT << "Processed undecryptable packet!";
+    ++stats_.packets_processed;
+    delete packet;
+    undecryptable_packets_.pop_front();
+  }
+
+  // Once forward secure encryption is in use, there will be no
+  // new keys installed and hence any undecryptable packets will
+  // never be able to be decrypted.
+  if (encryption_level_ == ENCRYPTION_FORWARD_SECURE) {
+    if (debug_visitor_.get() != nullptr) {
+      // TODO(rtenneti): perhaps more efficient to pass the number of
+      // undecryptable packets as the argument to OnUndecryptablePacket so that
+      // we just need to call OnUndecryptablePacket once?
+      for (size_t i = 0; i < undecryptable_packets_.size(); ++i) {
+        debug_visitor_->OnUndecryptablePacket();
+      }
+    }
+    STLDeleteElements(&undecryptable_packets_);
+  }
+}
+
+void QuicConnection::MaybeProcessRevivedPacket() {
+  QuicFecGroup* group = GetFecGroup();
+  if (!connected_ || group == nullptr || !group->CanRevive()) {
+    return;
+  }
+  QuicPacketHeader revived_header;
+  char revived_payload[kMaxPacketSize];
+  size_t len = group->Revive(&revived_header, revived_payload, kMaxPacketSize);
+  revived_header.public_header.connection_id = connection_id_;
+  revived_header.public_header.connection_id_length =
+      last_header_.public_header.connection_id_length;
+  revived_header.public_header.version_flag = false;
+  revived_header.public_header.reset_flag = false;
+  revived_header.public_header.sequence_number_length =
+      last_header_.public_header.sequence_number_length;
+  revived_header.fec_flag = false;
+  revived_header.is_in_fec_group = NOT_IN_FEC_GROUP;
+  revived_header.fec_group = 0;
+  group_map_.erase(last_header_.fec_group);
+  last_decrypted_packet_level_ = group->effective_encryption_level();
+  DCHECK_LT(last_decrypted_packet_level_, NUM_ENCRYPTION_LEVELS);
+  delete group;
+
+  last_packet_revived_ = true;
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnRevivedPacket(revived_header,
+                                    StringPiece(revived_payload, len));
+  }
+
+  ++stats_.packets_revived;
+  framer_.ProcessRevivedPacket(&revived_header,
+                               StringPiece(revived_payload, len));
+}
+
+QuicFecGroup* QuicConnection::GetFecGroup() {
+  QuicFecGroupNumber fec_group_num = last_header_.fec_group;
+  if (fec_group_num == 0) {
+    return nullptr;
+  }
+  if (group_map_.count(fec_group_num) == 0) {
+    if (group_map_.size() >= kMaxFecGroups) {  // Too many groups
+      if (fec_group_num < group_map_.begin()->first) {
+        // The group being requested is a group we've seen before and deleted.
+        // Don't recreate it.
+        return nullptr;
+      }
+      // Clear the lowest group number.
+      delete group_map_.begin()->second;
+      group_map_.erase(group_map_.begin());
+    }
+    group_map_[fec_group_num] = new QuicFecGroup();
+  }
+  return group_map_[fec_group_num];
+}
+
+void QuicConnection::SendConnectionClose(QuicErrorCode error) {
+  SendConnectionCloseWithDetails(error, string());
+}
+
+void QuicConnection::SendConnectionCloseWithDetails(QuicErrorCode error,
+                                                    const string& details) {
+  // If we're write blocked, WritePacket() will not send, but will capture the
+  // serialized packet.
+  SendConnectionClosePacket(error, details);
+  if (connected_) {
+    // It's possible that while sending the connection close packet, we get a
+    // socket error and disconnect right then and there.  Avoid a double
+    // disconnect in that case.
+    CloseConnection(error, false);
+  }
+}
+
+void QuicConnection::SendConnectionClosePacket(QuicErrorCode error,
+                                               const string& details) {
+  DVLOG(1) << ENDPOINT << "Force closing " << connection_id()
+           << " with error " << QuicUtils::ErrorToString(error)
+           << " (" << error << ") " << details;
+  ScopedPacketBundler ack_bundler(this, SEND_ACK);
+  QuicConnectionCloseFrame* frame = new QuicConnectionCloseFrame();
+  frame->error_code = error;
+  frame->error_details = details;
+  packet_generator_.AddControlFrame(QuicFrame(frame));
+  packet_generator_.FlushAllQueuedFrames();
+}
+
+void QuicConnection::CloseConnection(QuicErrorCode error, bool from_peer) {
+  if (!connected_) {
+    DLOG(DFATAL) << "Error: attempt to close an already closed connection"
+                 << base::debug::StackTrace().ToString();
+    return;
+  }
+  connected_ = false;
+  if (debug_visitor_.get() != nullptr) {
+    debug_visitor_->OnConnectionClosed(error, from_peer);
+  }
+  visitor_->OnConnectionClosed(error, from_peer);
+  // Cancel the alarms so they don't trigger any action now that the
+  // connection is closed.
+  ack_alarm_->Cancel();
+  ping_alarm_->Cancel();
+  resume_writes_alarm_->Cancel();
+  retransmission_alarm_->Cancel();
+  send_alarm_->Cancel();
+  timeout_alarm_->Cancel();
+}
+
+void QuicConnection::SendGoAway(QuicErrorCode error,
+                                QuicStreamId last_good_stream_id,
+                                const string& reason) {
+  DVLOG(1) << ENDPOINT << "Going away with error "
+           << QuicUtils::ErrorToString(error)
+           << " (" << error << ")";
+
+  // Opportunistically bundle an ack with this outgoing packet.
+  ScopedPacketBundler ack_bundler(this, BUNDLE_PENDING_ACK);
+  packet_generator_.AddControlFrame(
+      QuicFrame(new QuicGoAwayFrame(error, last_good_stream_id, reason)));
+}
+
+void QuicConnection::CloseFecGroupsBefore(
+    QuicPacketSequenceNumber sequence_number) {
+  FecGroupMap::iterator it = group_map_.begin();
+  while (it != group_map_.end()) {
+    // If this is the current group or the group doesn't protect this packet
+    // we can ignore it.
+    if (last_header_.fec_group == it->first ||
+        !it->second->ProtectsPacketsBefore(sequence_number)) {
+      ++it;
+      continue;
+    }
+    QuicFecGroup* fec_group = it->second;
+    DCHECK(!fec_group->CanRevive());
+    FecGroupMap::iterator next = it;
+    ++next;
+    group_map_.erase(it);
+    delete fec_group;
+    it = next;
+  }
+}
+
+size_t QuicConnection::max_packet_length() const {
+  return packet_generator_.max_packet_length();
+}
+
+void QuicConnection::set_max_packet_length(size_t length) {
+  return packet_generator_.set_max_packet_length(length);
+}
+
+bool QuicConnection::HasQueuedData() const {
+  return pending_version_negotiation_packet_ ||
+      !queued_packets_.empty() || packet_generator_.HasQueuedFrames();
+}
+
+bool QuicConnection::CanWriteStreamData() {
+  // Don't write stream data if there are negotiation or queued data packets
+  // to send. Otherwise, continue and bundle as many frames as possible.
+  if (pending_version_negotiation_packet_ || !queued_packets_.empty()) {
+    return false;
+  }
+
+  IsHandshake pending_handshake = visitor_->HasPendingHandshake() ?
+      IS_HANDSHAKE : NOT_HANDSHAKE;
+  // Sending queued packets may have caused the socket to become write blocked,
+  // or the congestion manager to prohibit sending.  If we've sent everything
+  // we had queued and we're still not blocked, let the visitor know it can
+  // write more.
+  return ShouldGeneratePacket(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+                              pending_handshake);
+}
+
+void QuicConnection::SetIdleNetworkTimeout(QuicTime::Delta timeout) {
+  // Adjust the idle timeout on client and server to prevent clients from
+  // sending requests to servers which have already closed the connection.
+  if (is_server_) {
+    timeout = timeout.Add(QuicTime::Delta::FromSeconds(1));
+  } else if (timeout > QuicTime::Delta::FromSeconds(1)) {
+    timeout = timeout.Subtract(QuicTime::Delta::FromSeconds(1));
+  }
+
+  if (timeout < idle_network_timeout_) {
+    idle_network_timeout_ = timeout;
+    if (FLAGS_quic_timeouts_only_from_alarms) {
+      SetTimeoutAlarm();
+    } else {
+      CheckForTimeout();
+    }
+  } else {
+    idle_network_timeout_ = timeout;
+  }
+}
+
+void QuicConnection::SetOverallConnectionTimeout(QuicTime::Delta timeout) {
+  if (timeout < overall_connection_timeout_) {
+    overall_connection_timeout_ = timeout;
+    if (FLAGS_quic_timeouts_only_from_alarms) {
+      SetTimeoutAlarm();
+    } else {
+      CheckForTimeout();
+    }
+  } else {
+    overall_connection_timeout_ = timeout;
+  }
+}
+
+void QuicConnection::SetNetworkTimeouts(QuicTime::Delta overall_timeout,
+                                        QuicTime::Delta idle_timeout) {
+  LOG_IF(DFATAL, idle_timeout > overall_timeout)
+      << "idle_timeout:" << idle_timeout.ToMilliseconds()
+      << " overall_timeout:" << overall_timeout.ToMilliseconds();
+  // Adjust the idle timeout on client and server to prevent clients from
+  // sending requests to servers which have already closed the connection.
+  if (is_server_) {
+    idle_timeout = idle_timeout.Add(QuicTime::Delta::FromSeconds(1));
+  } else if (idle_timeout > QuicTime::Delta::FromSeconds(1)) {
+    idle_timeout = idle_timeout.Subtract(QuicTime::Delta::FromSeconds(1));
+  }
+  overall_connection_timeout_ = overall_timeout;
+  idle_network_timeout_ = idle_timeout;
+
+  SetTimeoutAlarm();
+}
+
+void QuicConnection::CheckForTimeout() {
+  QuicTime now = clock_->ApproximateNow();
+  QuicTime time_of_last_packet = max(time_of_last_received_packet_,
+                                     time_of_last_sent_new_packet_);
+
+  // |delta| can be < 0 as |now| is approximate time but |time_of_last_packet|
+  // is accurate time. However, this should not change the behavior of
+  // timeout handling.
+  QuicTime::Delta idle_duration = now.Subtract(time_of_last_packet);
+  DVLOG(1) << ENDPOINT << "last packet "
+           << time_of_last_packet.ToDebuggingValue()
+           << " now:" << now.ToDebuggingValue()
+           << " idle_duration:" << idle_duration.ToMicroseconds()
+           << " idle_network_timeout: "
+           << idle_network_timeout_.ToMicroseconds();
+  if (idle_duration >= idle_network_timeout_) {
+    DVLOG(1) << ENDPOINT << "Connection timedout due to no network activity.";
+    SendConnectionClose(QUIC_CONNECTION_TIMED_OUT);
+    return;
+  }
+
+  if (!overall_connection_timeout_.IsInfinite()) {
+    QuicTime::Delta connected_duration =
+        now.Subtract(stats_.connection_creation_time);
+    DVLOG(1) << ENDPOINT << "connection time: "
+             << connected_duration.ToMicroseconds() << " overall timeout: "
+             << overall_connection_timeout_.ToMicroseconds();
+    if (connected_duration >= overall_connection_timeout_) {
+      DVLOG(1) << ENDPOINT <<
+          "Connection timedout due to overall connection timeout.";
+      SendConnectionClose(QUIC_CONNECTION_OVERALL_TIMED_OUT);
+      return;
+    }
+  }
+
+  SetTimeoutAlarm();
+}
+
+void QuicConnection::SetTimeoutAlarm() {
+  QuicTime time_of_last_packet = max(time_of_last_received_packet_,
+                                     time_of_last_sent_new_packet_);
+
+  QuicTime deadline = time_of_last_packet.Add(idle_network_timeout_);
+  if (!overall_connection_timeout_.IsInfinite()) {
+    deadline = min(deadline,
+                   stats_.connection_creation_time.Add(
+                       overall_connection_timeout_));
+  }
+
+  timeout_alarm_->Cancel();
+  timeout_alarm_->Set(deadline);
+}
+
+void QuicConnection::SetPingAlarm() {
+  if (is_server_) {
+    // Only clients send pings.
+    return;
+  }
+  if (!visitor_->HasOpenDataStreams()) {
+    ping_alarm_->Cancel();
+    // Don't send a ping unless there are open streams.
+    return;
+  }
+  QuicTime::Delta ping_timeout = QuicTime::Delta::FromSeconds(kPingTimeoutSecs);
+  ping_alarm_->Update(clock_->ApproximateNow().Add(ping_timeout),
+                      QuicTime::Delta::FromSeconds(1));
+}
+
+QuicConnection::ScopedPacketBundler::ScopedPacketBundler(
+    QuicConnection* connection,
+    AckBundling send_ack)
+    : connection_(connection),
+      already_in_batch_mode_(connection != nullptr &&
+                             connection->packet_generator_.InBatchMode()) {
+  if (connection_ == nullptr) {
+    return;
+  }
+  // Move generator into batch mode. If caller wants us to include an ack,
+  // check the delayed-ack timer to see if there's ack info to be sent.
+  if (!already_in_batch_mode_) {
+    DVLOG(1) << "Entering Batch Mode.";
+    connection_->packet_generator_.StartBatchOperations();
+  }
+  // Bundle an ack if the alarm is set or with every second packet if we need to
+  // raise the peer's least unacked.
+  bool ack_pending =
+      connection_->ack_alarm_->IsSet() || connection_->stop_waiting_count_ > 1;
+  if (send_ack == SEND_ACK || (send_ack == BUNDLE_PENDING_ACK && ack_pending)) {
+    DVLOG(1) << "Bundling ack with outgoing packet.";
+    connection_->SendAck();
+  }
+}
+
+QuicConnection::ScopedPacketBundler::~ScopedPacketBundler() {
+  if (connection_ == nullptr) {
+    return;
+  }
+  // If we changed the generator's batch state, restore original batch state.
+  if (!already_in_batch_mode_) {
+    DVLOG(1) << "Leaving Batch Mode.";
+    connection_->packet_generator_.FinishBatchOperations();
+  }
+  DCHECK_EQ(already_in_batch_mode_,
+            connection_->packet_generator_.InBatchMode());
+}
+
+HasRetransmittableData QuicConnection::IsRetransmittable(
+    const QueuedPacket& packet) {
+  // Retransmitted packets retransmittable frames are owned by the unacked
+  // packet map, but are not present in the serialized packet.
+  if (packet.transmission_type != NOT_RETRANSMISSION ||
+      packet.serialized_packet.retransmittable_frames != nullptr) {
+    return HAS_RETRANSMITTABLE_DATA;
+  } else {
+    return NO_RETRANSMITTABLE_DATA;
+  }
+}
+
+bool QuicConnection::IsConnectionClose(
+    QueuedPacket packet) {
+  RetransmittableFrames* retransmittable_frames =
+      packet.serialized_packet.retransmittable_frames;
+  if (!retransmittable_frames) {
+    return false;
+  }
+  for (size_t i = 0; i < retransmittable_frames->frames().size(); ++i) {
+    if (retransmittable_frames->frames()[i].type == CONNECTION_CLOSE_FRAME) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_connection.h b/net/quic/quic_connection.h
new file mode 100644
index 0000000..0b017b0
--- /dev/null
+++ b/net/quic/quic_connection.h
@@ -0,0 +1,820 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The entity that handles framing writes for a Quic client or server.
+// Each QuicSession will have a connection associated with it.
+//
+// On the server side, the Dispatcher handles the raw reads, and hands off
+// packets via ProcessUdpPacket for framing and processing.
+//
+// On the client side, the Connection handles the raw reads, as well as the
+// processing.
+//
+// Note: this class is not thread-safe.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_H_
+#define NET_QUIC_QUIC_CONNECTION_H_
+
+#include <stddef.h>
+#include <deque>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/base/iovec.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/iovector.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_ack_notifier_manager.h"
+#include "net/quic/quic_alarm.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_packet_generator.h"
+#include "net/quic/quic_packet_writer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_received_packet_manager.h"
+#include "net/quic/quic_sent_entropy_manager.h"
+#include "net/quic/quic_sent_packet_manager.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/quic_types.h"
+
+namespace net {
+
+class QuicClock;
+class QuicConfig;
+class QuicConnection;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicFecGroup;
+class QuicRandom;
+
+namespace test {
+class PacketSavingConnection;
+class QuicConnectionPeer;
+}  // namespace test
+
+// Class that receives callbacks from the connection when frames are received
+// and when other interesting events happen.
+class NET_EXPORT_PRIVATE QuicConnectionVisitorInterface {
+ public:
+  virtual ~QuicConnectionVisitorInterface() {}
+
+  // A simple visitor interface for dealing with data frames.
+  virtual void OnStreamFrames(const std::vector<QuicStreamFrame>& frames) = 0;
+
+  // The session should process all WINDOW_UPDATE frames, adjusting both stream
+  // and connection level flow control windows.
+  virtual void OnWindowUpdateFrames(
+      const std::vector<QuicWindowUpdateFrame>& frames) = 0;
+
+  // BLOCKED frames tell us that the peer believes it is flow control blocked on
+  // a specified stream. If the session at this end disagrees, something has
+  // gone wrong with our flow control accounting.
+  virtual void OnBlockedFrames(const std::vector<QuicBlockedFrame>& frames) = 0;
+
+  // Called when the stream is reset by the peer.
+  virtual void OnRstStream(const QuicRstStreamFrame& frame) = 0;
+
+  // Called when the connection is going away according to the peer.
+  virtual void OnGoAway(const QuicGoAwayFrame& frame) = 0;
+
+  // Called when the connection is closed either locally by the framer, or
+  // remotely by the peer.
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) = 0;
+
+  // Called when the connection failed to write because the socket was blocked.
+  virtual void OnWriteBlocked() = 0;
+
+  // Called once a specific QUIC version is agreed by both endpoints.
+  virtual void OnSuccessfulVersionNegotiation(const QuicVersion& version) = 0;
+
+  // Called when a blocked socket becomes writable.
+  virtual void OnCanWrite() = 0;
+
+  // Called when the connection experiences a change in congestion window.
+  virtual void OnCongestionWindowChange(QuicTime now) = 0;
+
+  // Called to ask if the visitor wants to schedule write resumption as it both
+  // has pending data to write, and is able to write (e.g. based on flow control
+  // limits).
+  // Writes may be pending because they were write-blocked, congestion-throttled
+  // or yielded to other connections.
+  virtual bool WillingAndAbleToWrite() const = 0;
+
+  // Called to ask if any handshake messages are pending in this visitor.
+  virtual bool HasPendingHandshake() const = 0;
+
+  // Called to ask if any streams are open in this visitor, excluding the
+  // reserved crypto and headers stream.
+  virtual bool HasOpenDataStreams() const = 0;
+};
+
+// Interface which gets callbacks from the QuicConnection at interesting
+// points.  Implementations must not mutate the state of the connection
+// as a result of these callbacks.
+class NET_EXPORT_PRIVATE QuicConnectionDebugVisitor
+    : public QuicPacketGenerator::DebugDelegate,
+      public QuicSentPacketManager::DebugDelegate {
+ public:
+  virtual ~QuicConnectionDebugVisitor() {}
+
+  // Called when a packet has been sent.
+  virtual void OnPacketSent(QuicPacketSequenceNumber sequence_number,
+                            QuicPacketSequenceNumber original_sequence_number,
+                            EncryptionLevel level,
+                            TransmissionType transmission_type,
+                            const QuicEncryptedPacket& packet,
+                            WriteResult result) {}
+
+  // Called when a packet has been received, but before it is
+  // validated or parsed.
+  virtual void OnPacketReceived(const IPEndPoint& self_address,
+                                const IPEndPoint& peer_address,
+                                const QuicEncryptedPacket& packet) {}
+
+  // Called when a packet is received with a connection id that does not
+  // match the ID of this connection.
+  virtual void OnIncorrectConnectionId(
+      QuicConnectionId connection_id) {}
+
+  // Called when an undecryptable packet has been received.
+  virtual void OnUndecryptablePacket() {}
+
+  // Called when a duplicate packet has been received.
+  virtual void OnDuplicatePacket(QuicPacketSequenceNumber sequence_number) {}
+
+  // Called when the protocol version on the received packet doensn't match
+  // current protocol version of the connection.
+  virtual void OnProtocolVersionMismatch(QuicVersion version) {}
+
+  // Called when the complete header of a packet has been parsed.
+  virtual void OnPacketHeader(const QuicPacketHeader& header) {}
+
+  // Called when a StreamFrame has been parsed.
+  virtual void OnStreamFrame(const QuicStreamFrame& frame) {}
+
+  // Called when a AckFrame has been parsed.
+  virtual void OnAckFrame(const QuicAckFrame& frame) {}
+
+  // Called when a CongestionFeedbackFrame has been parsed.
+  virtual void OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) {}
+
+  // Called when a StopWaitingFrame has been parsed.
+  virtual void OnStopWaitingFrame(const QuicStopWaitingFrame& frame) {}
+
+  // Called when a Ping has been parsed.
+  virtual void OnPingFrame(const QuicPingFrame& frame) {}
+
+  // Called when a GoAway has been parsed.
+  virtual void OnGoAwayFrame(const QuicGoAwayFrame& frame) {}
+
+  // Called when a RstStreamFrame has been parsed.
+  virtual void OnRstStreamFrame(const QuicRstStreamFrame& frame) {}
+
+  // Called when a ConnectionCloseFrame has been parsed.
+  virtual void OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) {}
+
+  // Called when a WindowUpdate has been parsed.
+  virtual void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) {}
+
+  // Called when a BlockedFrame has been parsed.
+  virtual void OnBlockedFrame(const QuicBlockedFrame& frame) {}
+
+  // Called when a public reset packet has been received.
+  virtual void OnPublicResetPacket(const QuicPublicResetPacket& packet) {}
+
+  // Called when a version negotiation packet has been received.
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) {}
+
+  // Called after a packet has been successfully parsed which results
+  // in the revival of a packet via FEC.
+  virtual void OnRevivedPacket(const QuicPacketHeader& revived_header,
+                               base::StringPiece payload) {}
+
+  // Called when the connection is closed.
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) {}
+
+  // Called when the version negotiation is successful.
+  virtual void OnSuccessfulVersionNegotiation(const QuicVersion& version) {}
+};
+
+class NET_EXPORT_PRIVATE QuicConnectionHelperInterface {
+ public:
+  virtual ~QuicConnectionHelperInterface() {}
+
+  // Returns a QuicClock to be used for all time related functions.
+  virtual const QuicClock* GetClock() const = 0;
+
+  // Returns a QuicRandom to be used for all random number related functions.
+  virtual QuicRandom* GetRandomGenerator() = 0;
+
+  // Creates a new platform-specific alarm which will be configured to
+  // notify |delegate| when the alarm fires.  Caller takes ownership
+  // of the new alarm, which will not yet be "set" to fire.
+  virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) = 0;
+};
+
+class NET_EXPORT_PRIVATE QuicConnection
+    : public QuicFramerVisitorInterface,
+      public QuicBlockedWriterInterface,
+      public QuicPacketGenerator::DelegateInterface,
+      public QuicSentPacketManager::NetworkChangeVisitor {
+ public:
+  enum AckBundling {
+    NO_ACK = 0,
+    SEND_ACK = 1,
+    BUNDLE_PENDING_ACK = 2,
+  };
+
+  class PacketWriterFactory {
+   public:
+    virtual ~PacketWriterFactory() {}
+
+    virtual QuicPacketWriter* Create(QuicConnection* connection) const = 0;
+  };
+
+  // Constructs a new QuicConnection for |connection_id| and |address|. Invokes
+  // writer_factory->Create() to get a writer; |owns_writer| specifies whether
+  // the connection takes ownership of the returned writer. |helper| must
+  // outlive this connection.
+  QuicConnection(QuicConnectionId connection_id,
+                 IPEndPoint address,
+                 QuicConnectionHelperInterface* helper,
+                 const PacketWriterFactory& writer_factory,
+                 bool owns_writer,
+                 bool is_server,
+                 const QuicVersionVector& supported_versions);
+  virtual ~QuicConnection();
+
+  // Sets connection parameters from the supplied |config|.
+  void SetFromConfig(const QuicConfig& config);
+
+  // Send the data in |data| to the peer in as few packets as possible.
+  // Returns a pair with the number of bytes consumed from data, and a boolean
+  // indicating if the fin bit was consumed.  This does not indicate the data
+  // has been sent on the wire: it may have been turned into a packet and queued
+  // if the socket was unexpectedly blocked. |fec_protection| indicates if
+  // data is to be FEC protected. Note that data that is sent immediately
+  // following MUST_FEC_PROTECT data may get protected by falling within the
+  // same FEC group.
+  // If |delegate| is provided, then it will be informed once ACKs have been
+  // received for all the packets written in this call.
+  // The |delegate| is not owned by the QuicConnection and must outlive it.
+  QuicConsumedData SendStreamData(QuicStreamId id,
+                                  const IOVector& data,
+                                  QuicStreamOffset offset,
+                                  bool fin,
+                                  FecProtection fec_protection,
+                                  QuicAckNotifier::DelegateInterface* delegate);
+
+  // Send a RST_STREAM frame to the peer.
+  virtual void SendRstStream(QuicStreamId id,
+                             QuicRstStreamErrorCode error,
+                             QuicStreamOffset bytes_written);
+
+  // Send a BLOCKED frame to the peer.
+  virtual void SendBlocked(QuicStreamId id);
+
+  // Send a WINDOW_UPDATE frame to the peer.
+  virtual void SendWindowUpdate(QuicStreamId id,
+                                QuicStreamOffset byte_offset);
+
+  // Sends the connection close packet without affecting the state of the
+  // connection.  This should only be called if the session is actively being
+  // destroyed: otherwise call SendConnectionCloseWithDetails instead.
+  virtual void SendConnectionClosePacket(QuicErrorCode error,
+                                         const std::string& details);
+
+  // Sends a connection close frame to the peer, and closes the connection by
+  // calling CloseConnection(notifying the visitor as it does so).
+  virtual void SendConnectionClose(QuicErrorCode error);
+  virtual void SendConnectionCloseWithDetails(QuicErrorCode error,
+                                              const std::string& details);
+  // Notifies the visitor of the close and marks the connection as disconnected.
+  virtual void CloseConnection(QuicErrorCode error, bool from_peer) OVERRIDE;
+  virtual void SendGoAway(QuicErrorCode error,
+                          QuicStreamId last_good_stream_id,
+                          const std::string& reason);
+
+  // Returns statistics tracked for this connection.
+  const QuicConnectionStats& GetStats();
+
+  // Processes an incoming UDP packet (consisting of a QuicEncryptedPacket) from
+  // the peer.  If processing this packet permits a packet to be revived from
+  // its FEC group that packet will be revived and processed.
+  virtual void ProcessUdpPacket(const IPEndPoint& self_address,
+                                const IPEndPoint& peer_address,
+                                const QuicEncryptedPacket& packet);
+
+  // QuicBlockedWriterInterface
+  // Called when the underlying connection becomes writable to allow queued
+  // writes to happen.
+  virtual void OnCanWrite() OVERRIDE;
+
+  // Called when an error occurs while attempting to write a packet to the
+  // network.
+  void OnWriteError(int error_code);
+
+  // If the socket is not blocked, writes queued packets.
+  void WriteIfNotBlocked();
+
+  // The version of the protocol this connection is using.
+  QuicVersion version() const { return framer_.version(); }
+
+  // The versions of the protocol that this connection supports.
+  const QuicVersionVector& supported_versions() const {
+    return framer_.supported_versions();
+  }
+
+  // From QuicFramerVisitorInterface
+  virtual void OnError(QuicFramer* framer) OVERRIDE;
+  virtual bool OnProtocolVersionMismatch(QuicVersion received_version) OVERRIDE;
+  virtual void OnPacket() OVERRIDE;
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) OVERRIDE;
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) OVERRIDE;
+  virtual void OnRevivedPacket() OVERRIDE;
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) OVERRIDE;
+  virtual bool OnUnauthenticatedHeader(const QuicPacketHeader& header) OVERRIDE;
+  virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE;
+  virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+  virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE;
+  virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+  virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+  virtual bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) OVERRIDE;
+  virtual bool OnPingFrame(const QuicPingFrame& frame) OVERRIDE;
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) OVERRIDE;
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+  virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) OVERRIDE;
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE;
+  virtual void OnFecData(const QuicFecData& fec) OVERRIDE;
+  virtual void OnPacketComplete() OVERRIDE;
+
+  // QuicPacketGenerator::DelegateInterface
+  virtual bool ShouldGeneratePacket(TransmissionType transmission_type,
+                                    HasRetransmittableData retransmittable,
+                                    IsHandshake handshake) OVERRIDE;
+  virtual QuicAckFrame* CreateAckFrame() OVERRIDE;
+  virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() OVERRIDE;
+  virtual QuicStopWaitingFrame* CreateStopWaitingFrame() OVERRIDE;
+  virtual void OnSerializedPacket(const SerializedPacket& packet) OVERRIDE;
+
+  // QuicSentPacketManager::NetworkChangeVisitor
+  virtual void OnCongestionWindowChange(
+      QuicByteCount congestion_window) OVERRIDE;
+
+  // Called by the crypto stream when the handshake completes. In the server's
+  // case this is when the SHLO has been ACKed. Clients call this on receipt of
+  // the SHLO.
+  void OnHandshakeComplete();
+
+  // Accessors
+  void set_visitor(QuicConnectionVisitorInterface* visitor) {
+    visitor_ = visitor;
+  }
+  // This method takes ownership of |debug_visitor|.
+  void set_debug_visitor(QuicConnectionDebugVisitor* debug_visitor) {
+    debug_visitor_.reset(debug_visitor);
+    packet_generator_.set_debug_delegate(debug_visitor);
+    sent_packet_manager_.set_debug_delegate(debug_visitor);
+  }
+  const IPEndPoint& self_address() const { return self_address_; }
+  const IPEndPoint& peer_address() const { return peer_address_; }
+  QuicConnectionId connection_id() const { return connection_id_; }
+  const QuicClock* clock() const { return clock_; }
+  QuicRandom* random_generator() const { return random_generator_; }
+  size_t max_packet_length() const;
+  void set_max_packet_length(size_t length);
+
+  bool connected() const { return connected_; }
+
+  // Must only be called on client connections.
+  const QuicVersionVector& server_supported_versions() const {
+    DCHECK(!is_server_);
+    return server_supported_versions_;
+  }
+
+  size_t NumFecGroups() const { return group_map_.size(); }
+
+  // Testing only.
+  size_t NumQueuedPackets() const { return queued_packets_.size(); }
+
+  QuicEncryptedPacket* ReleaseConnectionClosePacket() {
+    return connection_close_packet_.release();
+  }
+
+  // Returns true if the underlying UDP socket is writable, there is
+  // no queued data and the connection is not congestion-control
+  // blocked.
+  bool CanWriteStreamData();
+
+  // Returns true if the connection has queued packets or frames.
+  bool HasQueuedData() const;
+
+  // TODO(ianswett): Remove when quic_unified_timeouts is removed.
+  // Sets (or resets) the idle state connection timeout. Also, checks and times
+  // out the connection if network timer has expired for |timeout|.
+  void SetIdleNetworkTimeout(QuicTime::Delta timeout);
+  // Sets (or resets) the total time delta the connection can be alive for.
+  // Also, checks and times out the connection if timer has expired for
+  // |timeout|. Used to limit the time a connection can be alive before crypto
+  // handshake finishes.
+  void SetOverallConnectionTimeout(QuicTime::Delta timeout);
+
+  // Sets the overall and idle state connection timeouts.
+  // Times out the connection if the timeout has been reached and
+  // the quic_timeouts_only_from_alarms flag is false.
+  void SetNetworkTimeouts(QuicTime::Delta overall_timeout,
+                          QuicTime::Delta idle_timeout);
+
+  // If the connection has timed out, this will close the connection.
+  // Otherwise, it will reschedule the timeout alarm.
+  void CheckForTimeout();
+
+  // Sends a ping, and resets the ping alarm.
+  void SendPing();
+
+  // Sets up a packet with an QuicAckFrame and sends it out.
+  void SendAck();
+
+  // Called when an RTO fires.  Resets the retransmission alarm if there are
+  // remaining unacked packets.
+  void OnRetransmissionTimeout();
+
+  // Retransmits all unacked packets with retransmittable frames if
+  // |retransmission_type| is ALL_UNACKED_PACKETS, otherwise retransmits only
+  // initially encrypted packets. Used when the negotiated protocol version is
+  // different from what was initially assumed and when the initial encryption
+  // changes.
+  void RetransmitUnackedPackets(TransmissionType retransmission_type);
+
+  // Calls |sent_packet_manager_|'s NeuterUnencryptedPackets. Used when the
+  // connection becomes forward secure and hasn't received acks for all packets.
+  void NeuterUnencryptedPackets();
+
+  // Changes the encrypter used for level |level| to |encrypter|. The function
+  // takes ownership of |encrypter|.
+  void SetEncrypter(EncryptionLevel level, QuicEncrypter* encrypter);
+  const QuicEncrypter* encrypter(EncryptionLevel level) const;
+
+  // SetDefaultEncryptionLevel sets the encryption level that will be applied
+  // to new packets.
+  void SetDefaultEncryptionLevel(EncryptionLevel level);
+
+  // SetDecrypter sets the primary decrypter, replacing any that already exists,
+  // and takes ownership. If an alternative decrypter is in place then the
+  // function DCHECKs. This is intended for cases where one knows that future
+  // packets will be using the new decrypter and the previous decrypter is now
+  // obsolete. |level| indicates the encryption level of the new decrypter.
+  void SetDecrypter(QuicDecrypter* decrypter, EncryptionLevel level);
+
+  // SetAlternativeDecrypter sets a decrypter that may be used to decrypt
+  // future packets and takes ownership of it. |level| indicates the encryption
+  // level of the decrypter. If |latch_once_used| is true, then the first time
+  // that the decrypter is successful it will replace the primary decrypter.
+  // Otherwise both decrypters will remain active and the primary decrypter
+  // will be the one last used.
+  void SetAlternativeDecrypter(QuicDecrypter* decrypter,
+                               EncryptionLevel level,
+                               bool latch_once_used);
+
+  const QuicDecrypter* decrypter() const;
+  const QuicDecrypter* alternative_decrypter() const;
+
+  bool is_server() const { return is_server_; }
+
+  // Returns the underlying sent packet manager.
+  const QuicSentPacketManager& sent_packet_manager() const {
+    return sent_packet_manager_;
+  }
+
+  bool CanWrite(HasRetransmittableData retransmittable);
+
+  // Stores current batch state for connection, puts the connection
+  // into batch mode, and destruction restores the stored batch state.
+  // While the bundler is in scope, any generated frames are bundled
+  // as densely as possible into packets.  In addition, this bundler
+  // can be configured to ensure that an ACK frame is included in the
+  // first packet created, if there's new ack information to be sent.
+  class ScopedPacketBundler {
+   public:
+    // In addition to all outgoing frames being bundled when the
+    // bundler is in scope, setting |include_ack| to true ensures that
+    // an ACK frame is opportunistically bundled with the first
+    // outgoing packet.
+    ScopedPacketBundler(QuicConnection* connection, AckBundling send_ack);
+    ~ScopedPacketBundler();
+
+   private:
+    QuicConnection* connection_;
+    bool already_in_batch_mode_;
+  };
+
+ protected:
+  // Packets which have not been written to the wire.
+  // Owns the QuicPacket* packet.
+  struct QueuedPacket {
+    QueuedPacket(SerializedPacket packet,
+                 EncryptionLevel level);
+    QueuedPacket(SerializedPacket packet,
+                 EncryptionLevel level,
+                 TransmissionType transmission_type,
+                 QuicPacketSequenceNumber original_sequence_number);
+
+    SerializedPacket serialized_packet;
+    const EncryptionLevel encryption_level;
+    TransmissionType transmission_type;
+    // The packet's original sequence number if it is a retransmission.
+    // Otherwise it must be 0.
+    QuicPacketSequenceNumber original_sequence_number;
+  };
+
+  // Do any work which logically would be done in OnPacket but can not be
+  // safely done until the packet is validated.  Returns true if the packet
+  // can be handled, false otherwise.
+  virtual bool ProcessValidatedPacket();
+
+  // Send a packet to the peer, and takes ownership of the packet if the packet
+  // cannot be written immediately.
+  virtual void SendOrQueuePacket(QueuedPacket packet);
+
+  QuicConnectionHelperInterface* helper() { return helper_; }
+
+  // Selects and updates the version of the protocol being used by selecting a
+  // version from |available_versions| which is also supported. Returns true if
+  // such a version exists, false otherwise.
+  bool SelectMutualVersion(const QuicVersionVector& available_versions);
+
+  QuicPacketWriter* writer() { return writer_; }
+
+  bool peer_port_changed() const { return peer_port_changed_; }
+
+  QuicPacketSequenceNumber sequence_number_of_last_sent_packet() const {
+    return sequence_number_of_last_sent_packet_;
+  }
+
+ private:
+  friend class test::QuicConnectionPeer;
+  friend class test::PacketSavingConnection;
+
+  typedef std::list<QueuedPacket> QueuedPacketList;
+  typedef std::map<QuicFecGroupNumber, QuicFecGroup*> FecGroupMap;
+
+  // Writes the given packet to socket, encrypted with packet's
+  // encryption_level. Returns true on successful write, and false if the writer
+  // was blocked and the write needs to be tried again. Notifies the
+  // SentPacketManager when the write is successful and sets
+  // retransmittable frames to nullptr.
+  // Saves the connection close packet for later transmission, even if the
+  // writer is write blocked.
+  bool WritePacket(QueuedPacket* packet);
+
+  // Does the main work of WritePacket, but does not delete the packet or
+  // retransmittable frames upon success.
+  bool WritePacketInner(QueuedPacket* packet);
+
+  // Make sure an ack we got from our peer is sane.
+  bool ValidateAckFrame(const QuicAckFrame& incoming_ack);
+
+  // Make sure a stop waiting we got from our peer is sane.
+  bool ValidateStopWaitingFrame(const QuicStopWaitingFrame& stop_waiting);
+
+  // Sends a version negotiation packet to the peer.
+  void SendVersionNegotiationPacket();
+
+  // Clears any accumulated frames from the last received packet.
+  void ClearLastFrames();
+
+  // Writes as many queued packets as possible.  The connection must not be
+  // blocked when this is called.
+  void WriteQueuedPackets();
+
+  // Writes as many pending retransmissions as possible.
+  void WritePendingRetransmissions();
+
+  // Returns true if the packet should be discarded and not sent.
+  bool ShouldDiscardPacket(const QueuedPacket& packet);
+
+  // Queues |packet| in the hopes that it can be decrypted in the
+  // future, when a new key is installed.
+  void QueueUndecryptablePacket(const QuicEncryptedPacket& packet);
+
+  // Attempts to process any queued undecryptable packets.
+  void MaybeProcessUndecryptablePackets();
+
+  // If a packet can be revived from the current FEC group, then
+  // revive and process the packet.
+  void MaybeProcessRevivedPacket();
+
+  void ProcessAckFrame(const QuicAckFrame& incoming_ack);
+
+  void ProcessStopWaitingFrame(const QuicStopWaitingFrame& stop_waiting);
+
+  // Update |stop_waiting| for an outgoing ack.
+  void UpdateStopWaiting(QuicStopWaitingFrame* stop_waiting);
+
+  // Queues an ack or sets the ack alarm when an incoming packet arrives that
+  // should be acked.
+  void MaybeQueueAck();
+
+  // Checks if the last packet should instigate an ack.
+  bool ShouldLastPacketInstigateAck() const;
+
+  // Checks if the peer is waiting for packets that have been given up on, and
+  // therefore an ack frame should be sent with a larger least_unacked.
+  void UpdateStopWaitingCount();
+
+  // Sends any packets which are a response to the last packet, including both
+  // acks and pending writes if an ack opened the congestion window.
+  void MaybeSendInResponseToPacket();
+
+  // Gets the least unacked sequence number, which is the next sequence number
+  // to be sent if there are no outstanding packets.
+  QuicPacketSequenceNumber GetLeastUnacked() const;
+
+  // Get the FEC group associate with the last processed packet or nullptr, if
+  // the group has already been deleted.
+  QuicFecGroup* GetFecGroup();
+
+  // Closes any FEC groups protecting packets before |sequence_number|.
+  void CloseFecGroupsBefore(QuicPacketSequenceNumber sequence_number);
+
+  // Sets the timeout alarm to the appropriate value, if any.
+  void SetTimeoutAlarm();
+
+  // Sets the ping alarm to the appropriate value, if any.
+  void SetPingAlarm();
+
+  // On arrival of a new packet, checks to see if the socket addresses have
+  // changed since the last packet we saw on this connection.
+  void CheckForAddressMigration(const IPEndPoint& self_address,
+                                const IPEndPoint& peer_address);
+
+  HasRetransmittableData IsRetransmittable(const QueuedPacket& packet);
+  bool IsConnectionClose(QueuedPacket packet);
+
+  QuicFramer framer_;
+  QuicConnectionHelperInterface* helper_;  // Not owned.
+  QuicPacketWriter* writer_;  // Owned or not depending on |owns_writer_|.
+  bool owns_writer_;
+  EncryptionLevel encryption_level_;
+  const QuicClock* clock_;
+  QuicRandom* random_generator_;
+
+  const QuicConnectionId connection_id_;
+  // Address on the last successfully processed packet received from the
+  // client.
+  IPEndPoint self_address_;
+  IPEndPoint peer_address_;
+  // Used to store latest peer port to possibly migrate to later.
+  int migrating_peer_port_;
+
+  bool last_packet_revived_;  // True if the last packet was revived from FEC.
+  size_t last_size_;  // Size of the last received packet.
+  EncryptionLevel last_decrypted_packet_level_;
+  QuicPacketHeader last_header_;
+  std::vector<QuicStreamFrame> last_stream_frames_;
+  std::vector<QuicAckFrame> last_ack_frames_;
+  std::vector<QuicCongestionFeedbackFrame> last_congestion_frames_;
+  std::vector<QuicStopWaitingFrame> last_stop_waiting_frames_;
+  std::vector<QuicRstStreamFrame> last_rst_frames_;
+  std::vector<QuicGoAwayFrame> last_goaway_frames_;
+  std::vector<QuicWindowUpdateFrame> last_window_update_frames_;
+  std::vector<QuicBlockedFrame> last_blocked_frames_;
+  std::vector<QuicPingFrame> last_ping_frames_;
+  std::vector<QuicConnectionCloseFrame> last_close_frames_;
+
+  QuicCongestionFeedbackFrame outgoing_congestion_feedback_;
+
+  // Track some peer state so we can do less bookkeeping
+  // Largest sequence sent by the peer which had an ack frame (latest ack info).
+  QuicPacketSequenceNumber largest_seen_packet_with_ack_;
+
+  // Largest sequence number sent by the peer which had a stop waiting frame.
+  QuicPacketSequenceNumber largest_seen_packet_with_stop_waiting_;
+
+  // Collection of packets which were received before encryption was
+  // established, but which could not be decrypted.  We buffer these on
+  // the assumption that they could not be processed because they were
+  // sent with the INITIAL encryption and the CHLO message was lost.
+  std::deque<QuicEncryptedPacket*> undecryptable_packets_;
+
+  // When the version negotiation packet could not be sent because the socket
+  // was not writable, this is set to true.
+  bool pending_version_negotiation_packet_;
+
+  // When packets could not be sent because the socket was not writable,
+  // they are added to this list.  All corresponding frames are in
+  // unacked_packets_ if they are to be retransmitted.
+  QueuedPacketList queued_packets_;
+
+  // Contains the connection close packet if the connection has been closed.
+  scoped_ptr<QuicEncryptedPacket> connection_close_packet_;
+
+  FecGroupMap group_map_;
+
+  QuicReceivedPacketManager received_packet_manager_;
+  QuicSentEntropyManager sent_entropy_manager_;
+
+  // Indicates whether an ack should be sent the next time we try to write.
+  bool ack_queued_;
+  // Indicates how many consecutive packets have arrived without sending an ack.
+  uint32 num_packets_received_since_last_ack_sent_;
+  // Indicates how many consecutive times an ack has arrived which indicates
+  // the peer needs to stop waiting for some packets.
+  int stop_waiting_count_;
+
+  // An alarm that fires when an ACK should be sent to the peer.
+  scoped_ptr<QuicAlarm> ack_alarm_;
+  // An alarm that fires when a packet needs to be retransmitted.
+  scoped_ptr<QuicAlarm> retransmission_alarm_;
+  // An alarm that is scheduled when the sent scheduler requires a
+  // a delay before sending packets and fires when the packet may be sent.
+  scoped_ptr<QuicAlarm> send_alarm_;
+  // An alarm that is scheduled when the connection can still write and there
+  // may be more data to send.
+  scoped_ptr<QuicAlarm> resume_writes_alarm_;
+  // An alarm that fires when the connection may have timed out.
+  scoped_ptr<QuicAlarm> timeout_alarm_;
+  // An alarm that fires when a ping should be sent.
+  scoped_ptr<QuicAlarm> ping_alarm_;
+
+  QuicConnectionVisitorInterface* visitor_;
+  scoped_ptr<QuicConnectionDebugVisitor> debug_visitor_;
+  QuicPacketGenerator packet_generator_;
+
+  // Network idle time before we kill of this connection.
+  QuicTime::Delta idle_network_timeout_;
+  // Overall connection timeout.
+  QuicTime::Delta overall_connection_timeout_;
+
+  // Statistics for this session.
+  QuicConnectionStats stats_;
+
+  // The time that we got a packet for this connection.
+  // This is used for timeouts, and does not indicate the packet was processed.
+  QuicTime time_of_last_received_packet_;
+
+  // The last time a new (non-retransmitted) packet was sent for this
+  // connection.
+  QuicTime time_of_last_sent_new_packet_;
+
+  // Sequence number of the last sent packet.  Packets are guaranteed to be sent
+  // in sequence number order.
+  QuicPacketSequenceNumber sequence_number_of_last_sent_packet_;
+
+  // Sent packet manager which tracks the status of packets sent by this
+  // connection and contains the send and receive algorithms to determine when
+  // to send packets.
+  QuicSentPacketManager sent_packet_manager_;
+
+  // The state of connection in version negotiation finite state machine.
+  QuicVersionNegotiationState version_negotiation_state_;
+
+  // Tracks if the connection was created by the server.
+  bool is_server_;
+
+  // True by default.  False if we've received or sent an explicit connection
+  // close.
+  bool connected_;
+
+  // Set to true if the UDP packet headers have a new IP address for the peer.
+  // If true, do not perform connection migration.
+  bool peer_ip_changed_;
+
+  // Set to true if the UDP packet headers have a new port for the peer.
+  // If true, and the IP has not changed, then we can migrate the connection.
+  bool peer_port_changed_;
+
+  // Set to true if the UDP packet headers are addressed to a different IP.
+  // We do not support connection migration when the self IP changed.
+  bool self_ip_changed_;
+
+  // Set to true if the UDP packet headers are addressed to a different port.
+  // We do not support connection migration when the self port changed.
+  bool self_port_changed_;
+
+  // If non-empty this contains the set of versions received in a
+  // version negotiation packet.
+  QuicVersionVector server_supported_versions_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicConnection);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CONNECTION_H_
diff --git a/net/quic/quic_connection_helper.cc b/net/quic/quic_connection_helper.cc
new file mode 100644
index 0000000..fefd6fa
--- /dev/null
+++ b/net/quic/quic_connection_helper.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection_helper.h"
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/quic/quic_utils.h"
+
+namespace net {
+
+namespace {
+
+class QuicChromeAlarm : public QuicAlarm {
+ public:
+  QuicChromeAlarm(const QuicClock* clock,
+                  base::TaskRunner* task_runner,
+                  QuicAlarm::Delegate* delegate)
+      : QuicAlarm(delegate),
+        clock_(clock),
+        task_runner_(task_runner),
+        task_deadline_(QuicTime::Zero()),
+        weak_factory_(this) {}
+
+ protected:
+  virtual void SetImpl() OVERRIDE {
+    DCHECK(deadline().IsInitialized());
+    if (task_deadline_.IsInitialized()) {
+      if (task_deadline_ <= deadline()) {
+        // Since tasks can not be un-posted, OnAlarm will be invoked which
+        // will notice that deadline has not yet been reached, and will set
+        // the alarm for the new deadline.
+        return;
+      }
+      // The scheduled task is after new deadline.  Invalidate the weak ptrs
+      // so that task does not execute when we're not expecting it.
+      weak_factory_.InvalidateWeakPtrs();
+    }
+
+    int64 delay_us = deadline().Subtract(clock_->Now()).ToMicroseconds();
+    if (delay_us < 0) {
+      delay_us = 0;
+    }
+    task_runner_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&QuicChromeAlarm::OnAlarm, weak_factory_.GetWeakPtr()),
+        base::TimeDelta::FromMicroseconds(delay_us));
+    task_deadline_ = deadline();
+  }
+
+  virtual void CancelImpl() OVERRIDE {
+    DCHECK(!deadline().IsInitialized());
+    // Since tasks can not be un-posted, OnAlarm will be invoked which
+    // will notice that deadline is not Initialized and will do nothing.
+  }
+
+ private:
+  void OnAlarm() {
+    DCHECK(task_deadline_.IsInitialized());
+    task_deadline_ = QuicTime::Zero();
+    // The alarm may have been cancelled.
+    if (!deadline().IsInitialized()) {
+      return;
+    }
+
+    // The alarm may have been re-set to a later time.
+    if (clock_->Now() < deadline()) {
+      SetImpl();
+      return;
+    }
+
+    Fire();
+  }
+
+  const QuicClock* clock_;
+  base::TaskRunner* task_runner_;
+  // If a task has been posted to the message loop, this is the time it
+  // was scheduled to fire.  Tracking this allows us to avoid posting a
+  // new tast if the new deadline is in the future, but permits us to
+  // post a new task when the new deadline now earlier than when
+  // previously posted.
+  QuicTime task_deadline_;
+  base::WeakPtrFactory<QuicChromeAlarm> weak_factory_;
+};
+
+}  // namespace
+
+QuicConnectionHelper::QuicConnectionHelper(base::TaskRunner* task_runner,
+                                           const QuicClock* clock,
+                                           QuicRandom* random_generator)
+    : task_runner_(task_runner),
+      clock_(clock),
+      random_generator_(random_generator),
+      weak_factory_(this) {
+}
+
+QuicConnectionHelper::~QuicConnectionHelper() {
+}
+
+const QuicClock* QuicConnectionHelper::GetClock() const {
+  return clock_;
+}
+
+QuicRandom* QuicConnectionHelper::GetRandomGenerator() {
+  return random_generator_;
+}
+
+QuicAlarm* QuicConnectionHelper::CreateAlarm(QuicAlarm::Delegate* delegate) {
+  return new QuicChromeAlarm(clock_, task_runner_, delegate);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_connection_helper.h b/net/quic/quic_connection_helper.h
new file mode 100644
index 0000000..28cff52
--- /dev/null
+++ b/net/quic/quic_connection_helper.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The Chrome-specific helper for QuicConnection which uses
+// a TaskRunner for alarms, and uses a DatagramClientSocket for writing data.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_HELPER_H_
+#define NET_QUIC_QUIC_CONNECTION_HELPER_H_
+
+#include "net/quic/quic_connection.h"
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace base {
+class TaskRunner;
+}  // namespace base
+
+namespace net {
+
+class QuicClock;
+class QuicRandom;
+
+class NET_EXPORT_PRIVATE QuicConnectionHelper
+    : public QuicConnectionHelperInterface {
+ public:
+  QuicConnectionHelper(base::TaskRunner* task_runner,
+                       const QuicClock* clock,
+                       QuicRandom* random_generator);
+  virtual ~QuicConnectionHelper();
+
+  // QuicConnectionHelperInterface
+  virtual const QuicClock* GetClock() const OVERRIDE;
+  virtual QuicRandom* GetRandomGenerator() OVERRIDE;
+  virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE;
+
+ private:
+  base::TaskRunner* task_runner_;
+  const QuicClock* clock_;
+  QuicRandom* random_generator_;
+  base::WeakPtrFactory<QuicConnectionHelper> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicConnectionHelper);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CONNECTION_HELPER_H_
diff --git a/net/quic/quic_connection_helper_test.cc b/net/quic/quic_connection_helper_test.cc
new file mode 100644
index 0000000..638ca46
--- /dev/null
+++ b/net/quic/quic_connection_helper_test.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection_helper.h"
+
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/test_task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+class TestDelegate : public QuicAlarm::Delegate {
+ public:
+  TestDelegate() : fired_(false) {}
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    fired_ = true;
+    return QuicTime::Zero();
+  }
+
+  bool fired() const { return fired_; }
+  void Clear() { fired_= false; }
+
+ private:
+  bool fired_;
+};
+
+class QuicConnectionHelperTest : public ::testing::Test {
+ protected:
+  QuicConnectionHelperTest()
+      : runner_(new TestTaskRunner(&clock_)),
+        helper_(runner_.get(), &clock_, &random_generator_) {
+  }
+
+  scoped_refptr<TestTaskRunner> runner_;
+  QuicConnectionHelper helper_;
+  MockClock clock_;
+  MockRandom random_generator_;
+};
+
+TEST_F(QuicConnectionHelperTest, GetClock) {
+  EXPECT_EQ(&clock_, helper_.GetClock());
+}
+
+TEST_F(QuicConnectionHelperTest, GetRandomGenerator) {
+  EXPECT_EQ(&random_generator_, helper_.GetRandomGenerator());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarm) {
+  TestDelegate* delegate = new TestDelegate();
+  scoped_ptr<QuicAlarm> alarm(helper_.CreateAlarm(delegate));
+
+  QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+  alarm->Set(clock_.Now().Add(delta));
+
+  // Verify that the alarm task has been posted.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+            runner_->GetPostedTasks()[0].delay);
+
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+  EXPECT_TRUE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndCancel) {
+  TestDelegate* delegate = new TestDelegate();
+  scoped_ptr<QuicAlarm> alarm(helper_.CreateAlarm(delegate));
+
+  QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+  alarm->Set(clock_.Now().Add(delta));
+  alarm->Cancel();
+
+  // The alarm task should still be posted.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+            runner_->GetPostedTasks()[0].delay);
+
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+  EXPECT_FALSE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndReset) {
+  TestDelegate* delegate = new TestDelegate();
+  scoped_ptr<QuicAlarm> alarm(helper_.CreateAlarm(delegate));
+
+  QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+  alarm->Set(clock_.Now().Add(delta));
+  alarm->Cancel();
+  QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(3);
+  alarm->Set(clock_.Now().Add(new_delta));
+
+  // The alarm task should still be posted.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+            runner_->GetPostedTasks()[0].delay);
+
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+  EXPECT_FALSE(delegate->fired());
+
+  // The alarm task should be posted again.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(new_delta), clock_.Now());
+  EXPECT_TRUE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndResetEarlier) {
+  TestDelegate* delegate = new TestDelegate();
+  scoped_ptr<QuicAlarm> alarm(helper_.CreateAlarm(delegate));
+
+  QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(3);
+  alarm->Set(clock_.Now().Add(delta));
+  alarm->Cancel();
+  QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(1);
+  alarm->Set(clock_.Now().Add(new_delta));
+
+  // Both alarm tasks will be posted.
+  ASSERT_EQ(2u, runner_->GetPostedTasks().size());
+
+  // The earlier task will execute and will fire the alarm->
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(new_delta), clock_.Now());
+  EXPECT_TRUE(delegate->fired());
+  delegate->Clear();
+
+  // The latter task is still posted.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+
+  // When the latter task is executed, the weak ptr will be invalid and
+  // the alarm will not fire.
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+  EXPECT_FALSE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndUpdate) {
+  TestDelegate* delegate = new TestDelegate();
+  scoped_ptr<QuicAlarm> alarm(helper_.CreateAlarm(delegate));
+
+  const QuicClock* clock = helper_.GetClock();
+  QuicTime start = clock->Now();
+  QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+  alarm->Set(clock->Now().Add(delta));
+  QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(3);
+  alarm->Update(clock->Now().Add(new_delta),
+                QuicTime::Delta::FromMicroseconds(1));
+
+  // The alarm task should still be posted.
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+            runner_->GetPostedTasks()[0].delay);
+
+  runner_->RunNextTask();
+  EXPECT_EQ(QuicTime::Zero().Add(delta), clock->Now());
+  EXPECT_FALSE(delegate->fired());
+
+  // Move the alarm forward 1us and ensure it doesn't move forward.
+  alarm->Update(clock->Now().Add(new_delta),
+                QuicTime::Delta::FromMicroseconds(2));
+
+  ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+  EXPECT_EQ(
+      base::TimeDelta::FromMicroseconds(
+          new_delta.Subtract(delta).ToMicroseconds()),
+      runner_->GetPostedTasks()[0].delay);
+  runner_->RunNextTask();
+  EXPECT_EQ(start.Add(new_delta), clock->Now());
+  EXPECT_TRUE(delegate->fired());
+
+  // Set the alarm via an update call.
+  new_delta = QuicTime::Delta::FromMicroseconds(5);
+  alarm->Update(clock->Now().Add(new_delta),
+                QuicTime::Delta::FromMicroseconds(1));
+  EXPECT_TRUE(alarm->IsSet());
+
+  // Update it with an uninitialized time and ensure it's cancelled.
+  alarm->Update(QuicTime::Zero(), QuicTime::Delta::FromMicroseconds(1));
+  EXPECT_FALSE(alarm->IsSet());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_connection_logger.cc b/net/quic/quic_connection_logger.cc
new file mode 100644
index 0000000..4e40417
--- /dev/null
+++ b/net/quic/quic_connection_logger.cc
@@ -0,0 +1,889 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection_logger.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_address_mismatch.h"
+#include "net/quic/quic_socket_address_coder.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+namespace {
+
+// We have ranges-of-buckets in the cumulative histogram (covering 21 packet
+// sequences) of length 2, 3, 4, ... 22.
+// Hence the largest sample is bounded by the sum of those numbers.
+const int kBoundingSampleInCumulativeHistogram = ((2 + 22) * 21) / 2;
+
+base::Value* NetLogQuicPacketCallback(const IPEndPoint* self_address,
+                                      const IPEndPoint* peer_address,
+                                      size_t packet_size,
+                                      NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("self_address", self_address->ToString());
+  dict->SetString("peer_address", peer_address->ToString());
+  dict->SetInteger("size", packet_size);
+  return dict;
+}
+
+base::Value* NetLogQuicPacketSentCallback(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketSequenceNumber original_sequence_number,
+    EncryptionLevel level,
+    TransmissionType transmission_type,
+    size_t packet_size,
+    WriteResult result,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("encryption_level", level);
+  dict->SetInteger("transmission_type", transmission_type);
+  dict->SetString("packet_sequence_number",
+                  base::Uint64ToString(sequence_number));
+  dict->SetString("original_sequence_number",
+                  base::Uint64ToString(original_sequence_number));
+  dict->SetInteger("size", packet_size);
+  if (result.status != WRITE_STATUS_OK) {
+    dict->SetInteger("net_error", result.error_code);
+  }
+  return dict;
+}
+
+base::Value* NetLogQuicPacketHeaderCallback(const QuicPacketHeader* header,
+                                            NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("connection_id",
+                  base::Uint64ToString(header->public_header.connection_id));
+  dict->SetInteger("reset_flag", header->public_header.reset_flag);
+  dict->SetInteger("version_flag", header->public_header.version_flag);
+  dict->SetString("packet_sequence_number",
+                  base::Uint64ToString(header->packet_sequence_number));
+  dict->SetInteger("entropy_flag", header->entropy_flag);
+  dict->SetInteger("fec_flag", header->fec_flag);
+  dict->SetInteger("fec_group", header->fec_group);
+  return dict;
+}
+
+base::Value* NetLogQuicStreamFrameCallback(const QuicStreamFrame* frame,
+                                           NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("stream_id", frame->stream_id);
+  dict->SetBoolean("fin", frame->fin);
+  dict->SetString("offset", base::Uint64ToString(frame->offset));
+  dict->SetInteger("length", frame->data.TotalBufferSize());
+  return dict;
+}
+
+base::Value* NetLogQuicAckFrameCallback(const QuicAckFrame* frame,
+                                        NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("largest_observed",
+                  base::Uint64ToString(frame->largest_observed));
+  dict->SetInteger("delta_time_largest_observed_us",
+                   frame->delta_time_largest_observed.ToMicroseconds());
+  dict->SetInteger("entropy_hash",
+                   frame->entropy_hash);
+  dict->SetBoolean("truncated", frame->is_truncated);
+
+  base::ListValue* missing = new base::ListValue();
+  dict->Set("missing_packets", missing);
+  const SequenceNumberSet& missing_packets = frame->missing_packets;
+  for (SequenceNumberSet::const_iterator it = missing_packets.begin();
+       it != missing_packets.end(); ++it) {
+    missing->AppendString(base::Uint64ToString(*it));
+  }
+
+  base::ListValue* revived = new base::ListValue();
+  dict->Set("revived_packets", revived);
+  const SequenceNumberSet& revived_packets = frame->revived_packets;
+  for (SequenceNumberSet::const_iterator it = revived_packets.begin();
+       it != revived_packets.end(); ++it) {
+    revived->AppendString(base::Uint64ToString(*it));
+  }
+
+  base::ListValue* received = new base::ListValue();
+  dict->Set("received_packet_times", received);
+  const PacketTimeList& received_times = frame->received_packet_times;
+  for (PacketTimeList::const_iterator it = received_times.begin();
+       it != received_times.end(); ++it) {
+    base::DictionaryValue* info = new base::DictionaryValue();
+    info->SetInteger("sequence_number", it->first);
+    info->SetInteger("received", it->second.ToDebuggingValue());
+    received->Append(info);
+  }
+
+  return dict;
+}
+
+base::Value* NetLogQuicCongestionFeedbackFrameCallback(
+    const QuicCongestionFeedbackFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  switch (frame->type) {
+    case kTCP:
+      dict->SetString("type", "TCP");
+      dict->SetInteger("receive_window", frame->tcp.receive_window);
+      break;
+  }
+
+  return dict;
+}
+
+base::Value* NetLogQuicRstStreamFrameCallback(
+    const QuicRstStreamFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("stream_id", frame->stream_id);
+  dict->SetInteger("quic_rst_stream_error", frame->error_code);
+  dict->SetString("details", frame->error_details);
+  return dict;
+}
+
+base::Value* NetLogQuicConnectionCloseFrameCallback(
+    const QuicConnectionCloseFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("quic_error", frame->error_code);
+  dict->SetString("details", frame->error_details);
+  return dict;
+}
+
+base::Value* NetLogQuicWindowUpdateFrameCallback(
+    const QuicWindowUpdateFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("stream_id", frame->stream_id);
+  dict->SetString("byte_offset", base::Uint64ToString(frame->byte_offset));
+  return dict;
+}
+
+base::Value* NetLogQuicBlockedFrameCallback(
+    const QuicBlockedFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("stream_id", frame->stream_id);
+  return dict;
+}
+
+base::Value* NetLogQuicGoAwayFrameCallback(
+    const QuicGoAwayFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("quic_error", frame->error_code);
+  dict->SetInteger("last_good_stream_id", frame->last_good_stream_id);
+  dict->SetString("reason_phrase", frame->reason_phrase);
+  return dict;
+}
+
+base::Value* NetLogQuicStopWaitingFrameCallback(
+    const QuicStopWaitingFrame* frame,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  base::DictionaryValue* sent_info = new base::DictionaryValue();
+  dict->Set("sent_info", sent_info);
+  sent_info->SetString("least_unacked",
+                       base::Uint64ToString(frame->least_unacked));
+  return dict;
+}
+
+base::Value* NetLogQuicVersionNegotiationPacketCallback(
+    const QuicVersionNegotiationPacket* packet,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  base::ListValue* versions = new base::ListValue();
+  dict->Set("versions", versions);
+  for (QuicVersionVector::const_iterator it = packet->versions.begin();
+       it != packet->versions.end(); ++it) {
+    versions->AppendString(QuicVersionToString(*it));
+  }
+  return dict;
+}
+
+base::Value* NetLogQuicCryptoHandshakeMessageCallback(
+    const CryptoHandshakeMessage* message,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetString("quic_crypto_handshake_message", message->DebugString());
+  return dict;
+}
+
+base::Value* NetLogQuicOnConnectionClosedCallback(
+    QuicErrorCode error,
+    bool from_peer,
+    NetLog::LogLevel /* log_level */) {
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  dict->SetInteger("quic_error", error);
+  dict->SetBoolean("from_peer", from_peer);
+  return dict;
+}
+
+base::Value* NetLogQuicCertificateVerifiedCallback(
+    scoped_refptr<X509Certificate> cert,
+    NetLog::LogLevel /* log_level */) {
+  // Only the subjects are logged so that we can investigate connection pooling.
+  // More fields could be logged in the future.
+  std::vector<std::string> dns_names;
+  cert->GetDNSNames(&dns_names);
+  base::DictionaryValue* dict = new base::DictionaryValue();
+  base::ListValue* subjects = new base::ListValue();
+  for (std::vector<std::string>::const_iterator it = dns_names.begin();
+       it != dns_names.end(); it++) {
+    subjects->Append(new base::StringValue(*it));
+  }
+  dict->Set("subjects", subjects);
+  return dict;
+}
+
+void UpdatePacketGapSentHistogram(size_t num_consecutive_missing_packets) {
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.PacketGapSent",
+                       num_consecutive_missing_packets);
+}
+
+void UpdatePublicResetAddressMismatchHistogram(
+    const IPEndPoint& server_hello_address,
+    const IPEndPoint& public_reset_address) {
+  int sample = GetAddressMismatch(server_hello_address, public_reset_address);
+  // We are seemingly talking to an older server that does not support the
+  // feature, so we can't report the results in the histogram.
+  if (sample < 0) {
+    return;
+  }
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.PublicResetAddressMismatch2",
+                            sample, QUIC_ADDRESS_MISMATCH_MAX);
+}
+
+const char* GetConnectionDescriptionString() {
+  NetworkChangeNotifier::ConnectionType type =
+      NetworkChangeNotifier::GetConnectionType();
+  const char* description = NetworkChangeNotifier::ConnectionTypeToString(type);
+  // Most platforms don't distingish Wifi vs Etherenet, and call everything
+  // CONNECTION_UNKNOWN :-(.  We'll tease out some details when we are on WiFi,
+  // and hopefully leave only ethernet (with no WiFi available) in the
+  // CONNECTION_UNKNOWN category.  This *might* err if there is both ethernet,
+  // as well as WiFi, where WiFi was not being used that much.
+  // This function only seems usefully defined on Windows currently.
+  if (type == NetworkChangeNotifier::CONNECTION_UNKNOWN ||
+      type == NetworkChangeNotifier::CONNECTION_WIFI) {
+    WifiPHYLayerProtocol wifi_type = GetWifiPHYLayerProtocol();
+    switch (wifi_type) {
+      case WIFI_PHY_LAYER_PROTOCOL_NONE:
+        // No wifi support or no associated AP.
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_ANCIENT:
+        // An obsolete modes introduced by the original 802.11, e.g. IR, FHSS.
+        description = "CONNECTION_WIFI_ANCIENT";
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_A:
+        // 802.11a, OFDM-based rates.
+        description = "CONNECTION_WIFI_802.11a";
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_B:
+        // 802.11b, DSSS or HR DSSS.
+        description = "CONNECTION_WIFI_802.11b";
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_G:
+        // 802.11g, same rates as 802.11a but compatible with 802.11b.
+        description = "CONNECTION_WIFI_802.11g";
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_N:
+        // 802.11n, HT rates.
+        description = "CONNECTION_WIFI_802.11n";
+        break;
+      case WIFI_PHY_LAYER_PROTOCOL_UNKNOWN:
+        // Unclassified mode or failure to identify.
+        break;
+    }
+  }
+  return description;
+}
+
+// If |address| is an IPv4-mapped IPv6 address, returns ADDRESS_FAMILY_IPV4
+// instead of ADDRESS_FAMILY_IPV6. Othewise, behaves like GetAddressFamily().
+AddressFamily GetRealAddressFamily(const IPAddressNumber& address) {
+  return IsIPv4Mapped(address) ? ADDRESS_FAMILY_IPV4 :
+      GetAddressFamily(address);
+}
+
+}  // namespace
+
+QuicConnectionLogger::QuicConnectionLogger(const BoundNetLog& net_log)
+    : net_log_(net_log),
+      last_received_packet_sequence_number_(0),
+      last_received_packet_size_(0),
+      largest_received_packet_sequence_number_(0),
+      largest_received_missing_packet_sequence_number_(0),
+      num_out_of_order_received_packets_(0),
+      num_packets_received_(0),
+      num_truncated_acks_sent_(0),
+      num_truncated_acks_received_(0),
+      num_frames_received_(0),
+      num_duplicate_frames_received_(0),
+      num_incorrect_connection_ids_(0),
+      num_undecryptable_packets_(0),
+      num_duplicate_packets_(0),
+      connection_description_(GetConnectionDescriptionString()) {
+}
+
+QuicConnectionLogger::~QuicConnectionLogger() {
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.OutOfOrderPacketsReceived",
+                       num_out_of_order_received_packets_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.TruncatedAcksSent",
+                       num_truncated_acks_sent_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.TruncatedAcksReceived",
+                       num_truncated_acks_received_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.IncorrectConnectionIDsReceived",
+                       num_incorrect_connection_ids_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.UndecryptablePacketsReceived",
+                       num_undecryptable_packets_);
+  UMA_HISTOGRAM_COUNTS("Net.QuicSession.DuplicatePacketsReceived",
+                       num_duplicate_packets_);
+
+  if (num_frames_received_ > 0) {
+    int duplicate_stream_frame_per_thousand =
+        num_duplicate_frames_received_ * 1000 / num_frames_received_;
+    if (num_packets_received_ < 100) {
+      UMA_HISTOGRAM_CUSTOM_COUNTS(
+          "Net.QuicSession.StreamFrameDuplicatedShortConnection",
+          duplicate_stream_frame_per_thousand, 1, 1000, 75);
+    } else {
+      UMA_HISTOGRAM_CUSTOM_COUNTS(
+          "Net.QuicSession.StreamFrameDuplicatedLongConnection",
+          duplicate_stream_frame_per_thousand, 1, 1000, 75);
+
+    }
+  }
+
+  RecordLossHistograms();
+}
+
+void QuicConnectionLogger::OnFrameAddedToPacket(const QuicFrame& frame) {
+  switch (frame.type) {
+    case PADDING_FRAME:
+      break;
+    case STREAM_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_SENT,
+          base::Bind(&NetLogQuicStreamFrameCallback, frame.stream_frame));
+      break;
+    case ACK_FRAME: {
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_ACK_FRAME_SENT,
+          base::Bind(&NetLogQuicAckFrameCallback, frame.ack_frame));
+      const SequenceNumberSet& missing_packets =
+          frame.ack_frame->missing_packets;
+      const uint8 max_ranges = std::numeric_limits<uint8>::max();
+      // Compute an upper bound on the number of NACK ranges. If the bound
+      // is below the max, then it clearly isn't truncated.
+      if (missing_packets.size() < max_ranges ||
+          (*missing_packets.rbegin() - *missing_packets.begin() -
+           missing_packets.size() + 1) < max_ranges) {
+        break;
+      }
+      size_t num_ranges = 0;
+      QuicPacketSequenceNumber last_missing = 0;
+      for (SequenceNumberSet::const_iterator it = missing_packets.begin();
+           it != missing_packets.end(); ++it) {
+        if (*it != last_missing + 1 && ++num_ranges >= max_ranges) {
+          ++num_truncated_acks_sent_;
+          break;
+        }
+        last_missing = *it;
+      }
+      break;
+    }
+    case CONGESTION_FEEDBACK_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_SENT,
+          base::Bind(&NetLogQuicCongestionFeedbackFrameCallback,
+                     frame.congestion_feedback_frame));
+      break;
+    case RST_STREAM_FRAME:
+      UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.RstStreamErrorCodeClient",
+                                  frame.rst_stream_frame->error_code);
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_RST_STREAM_FRAME_SENT,
+          base::Bind(&NetLogQuicRstStreamFrameCallback,
+                     frame.rst_stream_frame));
+      break;
+    case CONNECTION_CLOSE_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_CONNECTION_CLOSE_FRAME_SENT,
+          base::Bind(&NetLogQuicConnectionCloseFrameCallback,
+                     frame.connection_close_frame));
+      break;
+    case GOAWAY_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_GOAWAY_FRAME_SENT,
+          base::Bind(&NetLogQuicGoAwayFrameCallback,
+                     frame.goaway_frame));
+      break;
+    case WINDOW_UPDATE_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_WINDOW_UPDATE_FRAME_SENT,
+          base::Bind(&NetLogQuicWindowUpdateFrameCallback,
+                     frame.window_update_frame));
+      break;
+    case BLOCKED_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_BLOCKED_FRAME_SENT,
+          base::Bind(&NetLogQuicBlockedFrameCallback,
+                     frame.blocked_frame));
+      break;
+    case STOP_WAITING_FRAME:
+      net_log_.AddEvent(
+          NetLog::TYPE_QUIC_SESSION_STOP_WAITING_FRAME_SENT,
+          base::Bind(&NetLogQuicStopWaitingFrameCallback,
+                     frame.stop_waiting_frame));
+      break;
+    case PING_FRAME:
+      // PingFrame has no contents to log, so just record that it was sent.
+      net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_PING_FRAME_SENT);
+      break;
+    default:
+      DCHECK(false) << "Illegal frame type: " << frame.type;
+  }
+}
+
+void QuicConnectionLogger::OnPacketSent(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketSequenceNumber original_sequence_number,
+    EncryptionLevel level,
+    TransmissionType transmission_type,
+    const QuicEncryptedPacket& packet,
+    WriteResult result) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_PACKET_SENT,
+      base::Bind(&NetLogQuicPacketSentCallback, sequence_number,
+                 original_sequence_number, level, transmission_type,
+                 packet.length(), result));
+}
+
+void QuicConnectionLogger::OnPacketReceived(const IPEndPoint& self_address,
+                                            const IPEndPoint& peer_address,
+                                            const QuicEncryptedPacket& packet) {
+  if (local_address_from_self_.GetFamily() == ADDRESS_FAMILY_UNSPECIFIED) {
+    local_address_from_self_ = self_address;
+    UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.ConnectionTypeFromSelf",
+                              GetRealAddressFamily(self_address.address()),
+                              ADDRESS_FAMILY_LAST);
+  }
+
+  last_received_packet_size_ = packet.length();
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_PACKET_RECEIVED,
+      base::Bind(&NetLogQuicPacketCallback, &self_address, &peer_address,
+                 packet.length()));
+}
+
+void QuicConnectionLogger::OnIncorrectConnectionId(
+    QuicConnectionId connection_id) {
+  ++num_incorrect_connection_ids_;
+}
+
+void QuicConnectionLogger::OnUndecryptablePacket() {
+  ++num_undecryptable_packets_;
+}
+
+void QuicConnectionLogger::OnDuplicatePacket(
+    QuicPacketSequenceNumber sequence_number) {
+  ++num_duplicate_packets_;
+}
+
+void QuicConnectionLogger::OnProtocolVersionMismatch(
+    QuicVersion received_version) {
+  // TODO(rtenneti): Add logging.
+}
+
+void QuicConnectionLogger::OnPacketHeader(const QuicPacketHeader& header) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED,
+      base::Bind(&NetLogQuicPacketHeaderCallback, &header));
+  ++num_packets_received_;
+  if (largest_received_packet_sequence_number_ <
+      header.packet_sequence_number) {
+    QuicPacketSequenceNumber delta = header.packet_sequence_number -
+        largest_received_packet_sequence_number_;
+    if (delta > 1) {
+      // There is a gap between the largest packet previously received and
+      // the current packet.  This indicates either loss, or out-of-order
+      // delivery.
+      UMA_HISTOGRAM_COUNTS("Net.QuicSession.PacketGapReceived", delta - 1);
+    }
+    largest_received_packet_sequence_number_ = header.packet_sequence_number;
+  }
+  if (header.packet_sequence_number < received_packets_.size())
+    received_packets_[header.packet_sequence_number] = true;
+  if (header.packet_sequence_number < last_received_packet_sequence_number_) {
+    ++num_out_of_order_received_packets_;
+    UMA_HISTOGRAM_COUNTS("Net.QuicSession.OutOfOrderGapReceived",
+                         last_received_packet_sequence_number_ -
+                             header.packet_sequence_number);
+  }
+  last_received_packet_sequence_number_ = header.packet_sequence_number;
+}
+
+void QuicConnectionLogger::OnStreamFrame(const QuicStreamFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicStreamFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnAckFrame(const QuicAckFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_ACK_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicAckFrameCallback, &frame));
+
+  const size_t kApproximateLargestSoloAckBytes = 100;
+  if (last_received_packet_sequence_number_ < received_acks_.size() &&
+      last_received_packet_size_ < kApproximateLargestSoloAckBytes)
+    received_acks_[last_received_packet_sequence_number_] = true;
+
+  if (frame.is_truncated)
+    ++num_truncated_acks_received_;
+
+  if (frame.missing_packets.empty())
+    return;
+
+  SequenceNumberSet missing_packets = frame.missing_packets;
+  SequenceNumberSet::const_iterator it = missing_packets.lower_bound(
+      largest_received_missing_packet_sequence_number_);
+  if (it == missing_packets.end())
+    return;
+
+  if (*it == largest_received_missing_packet_sequence_number_) {
+    ++it;
+    if (it == missing_packets.end())
+      return;
+  }
+  // Scan through the list and log consecutive ranges of missing packets.
+  size_t num_consecutive_missing_packets = 0;
+  QuicPacketSequenceNumber previous_missing_packet = *it - 1;
+  while (it != missing_packets.end()) {
+    if (previous_missing_packet == *it - 1) {
+      ++num_consecutive_missing_packets;
+    } else {
+      DCHECK_NE(0u, num_consecutive_missing_packets);
+      UpdatePacketGapSentHistogram(num_consecutive_missing_packets);
+      // Make sure this packet it included in the count.
+      num_consecutive_missing_packets = 1;
+    }
+    previous_missing_packet = *it;
+    ++it;
+  }
+  if (num_consecutive_missing_packets != 0) {
+    UpdatePacketGapSentHistogram(num_consecutive_missing_packets);
+  }
+  largest_received_missing_packet_sequence_number_ =
+        *missing_packets.rbegin();
+}
+
+void QuicConnectionLogger::OnCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicCongestionFeedbackFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnStopWaitingFrame(
+    const QuicStopWaitingFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_STOP_WAITING_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicStopWaitingFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnRstStreamFrame(const QuicRstStreamFrame& frame) {
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.RstStreamErrorCodeServer",
+                              frame.error_code);
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_RST_STREAM_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicRstStreamFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnConnectionCloseFrame(
+    const QuicConnectionCloseFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CONNECTION_CLOSE_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicConnectionCloseFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnWindowUpdateFrame(
+    const QuicWindowUpdateFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_WINDOW_UPDATE_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicWindowUpdateFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnBlockedFrame(const QuicBlockedFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_BLOCKED_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicBlockedFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_GOAWAY_FRAME_RECEIVED,
+      base::Bind(&NetLogQuicGoAwayFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnPingFrame(const QuicPingFrame& frame) {
+  // PingFrame has no contents to log, so just record that it was received.
+  net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_PING_FRAME_RECEIVED);
+}
+
+void QuicConnectionLogger::OnPublicResetPacket(
+    const QuicPublicResetPacket& packet) {
+  net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_PUBLIC_RESET_PACKET_RECEIVED);
+  UpdatePublicResetAddressMismatchHistogram(local_address_from_shlo_,
+                                            packet.client_address);
+}
+
+void QuicConnectionLogger::OnVersionNegotiationPacket(
+    const QuicVersionNegotiationPacket& packet) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_VERSION_NEGOTIATION_PACKET_RECEIVED,
+      base::Bind(&NetLogQuicVersionNegotiationPacketCallback, &packet));
+}
+
+void QuicConnectionLogger::OnRevivedPacket(
+    const QuicPacketHeader& revived_header,
+    base::StringPiece payload) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_REVIVED,
+      base::Bind(&NetLogQuicPacketHeaderCallback, &revived_header));
+}
+
+void QuicConnectionLogger::OnCryptoHandshakeMessageReceived(
+    const CryptoHandshakeMessage& message) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_RECEIVED,
+      base::Bind(&NetLogQuicCryptoHandshakeMessageCallback, &message));
+
+  if (message.tag() == kSHLO) {
+    StringPiece address;
+    QuicSocketAddressCoder decoder;
+    if (message.GetStringPiece(kCADR, &address) &&
+        decoder.Decode(address.data(), address.size())) {
+      local_address_from_shlo_ = IPEndPoint(decoder.ip(), decoder.port());
+      UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.ConnectionTypeFromPeer",
+                                GetRealAddressFamily(
+                                    local_address_from_shlo_.address()),
+                                ADDRESS_FAMILY_LAST);
+    }
+  }
+}
+
+void QuicConnectionLogger::OnCryptoHandshakeMessageSent(
+    const CryptoHandshakeMessage& message) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_SENT,
+      base::Bind(&NetLogQuicCryptoHandshakeMessageCallback, &message));
+}
+
+void QuicConnectionLogger::OnConnectionClosed(QuicErrorCode error,
+                                              bool from_peer) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CLOSED,
+      base::Bind(&NetLogQuicOnConnectionClosedCallback, error, from_peer));
+}
+
+void QuicConnectionLogger::OnSuccessfulVersionNegotiation(
+    const QuicVersion& version) {
+  string quic_version = QuicVersionToString(version);
+  net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_VERSION_NEGOTIATED,
+                    NetLog::StringCallback("version", &quic_version));
+}
+
+void QuicConnectionLogger::UpdateReceivedFrameCounts(
+    QuicStreamId stream_id,
+    int num_frames_received,
+    int num_duplicate_frames_received) {
+  if (stream_id != kCryptoStreamId) {
+    num_frames_received_ += num_frames_received;
+    num_duplicate_frames_received_ += num_duplicate_frames_received;
+  }
+}
+
+void QuicConnectionLogger::OnCertificateVerified(
+    const CertVerifyResult& result) {
+  net_log_.AddEvent(
+      NetLog::TYPE_QUIC_SESSION_CERTIFICATE_VERIFIED,
+      base::Bind(&NetLogQuicCertificateVerifiedCallback, result.verified_cert));
+}
+
+base::HistogramBase* QuicConnectionLogger::GetPacketSequenceNumberHistogram(
+    const char* statistic_name) const {
+  string prefix("Net.QuicSession.PacketReceived_");
+  return base::LinearHistogram::FactoryGet(
+      prefix + statistic_name + connection_description_,
+      1, received_packets_.size(), received_packets_.size() + 1,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+base::HistogramBase* QuicConnectionLogger::Get6PacketHistogram(
+    const char* which_6) const {
+  // This histogram takes a binary encoding of the 6 consecutive packets
+  // received.  As a result, there are 64 possible sample-patterns.
+  string prefix("Net.QuicSession.6PacketsPatternsReceived_");
+  return base::LinearHistogram::FactoryGet(
+      prefix + which_6 + connection_description_, 1, 64, 65,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+base::HistogramBase* QuicConnectionLogger::Get21CumulativeHistogram(
+  const char* which_21) const {
+  // This histogram contains, for each sequence of 21 packets, the results from
+  // 21 distinct questions about that sequence.  Conceptually the histogtram is
+  // broken into 21 distinct ranges, and one sample is added into each of those
+  // ranges whenever we process a set of 21 packets.
+  // There is a little rendundancy, as each "range" must have the same number
+  // of samples, all told, but the histogram is a tad easier to read this way.
+  // The questions are:
+  // Was the first packet present (bucket 0==>no; bucket 1==>yes)
+  // Of the first two packets, how many were present? (bucket 2==> none;
+  //   bucket 3==> 1 of 2; bucket 4==> 2 of 2)
+  // Of the  first three packets, how many were present? (bucket 5==>none;
+  //   bucket 6==> 1 of 3; bucket 7==> 2 of 3; bucket 8==> 3 of 3).
+  // etc.
+  string prefix("Net.QuicSession.21CumulativePacketsReceived_");
+  return base::LinearHistogram::FactoryGet(
+      prefix + which_21 + connection_description_,
+      1, kBoundingSampleInCumulativeHistogram,
+      kBoundingSampleInCumulativeHistogram + 1,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+// static
+void QuicConnectionLogger::AddTo21CumulativeHistogram(
+    base::HistogramBase* histogram,
+    int bit_mask_of_packets,
+    int valid_bits_in_mask) {
+  DCHECK_LE(valid_bits_in_mask, 21);
+  DCHECK_LT(bit_mask_of_packets, 1 << 21);
+  const int blank_bits_in_mask = 21 - valid_bits_in_mask;
+  DCHECK_EQ(bit_mask_of_packets & ((1 << blank_bits_in_mask) - 1), 0);
+  bit_mask_of_packets >>= blank_bits_in_mask;
+  int bits_so_far = 0;
+  int range_start = 0;
+  for (int i = 1; i <= valid_bits_in_mask; ++i) {
+    bits_so_far += bit_mask_of_packets & 1;
+    bit_mask_of_packets >>= 1;
+    DCHECK_LT(range_start + bits_so_far, kBoundingSampleInCumulativeHistogram);
+    histogram->Add(range_start + bits_so_far);
+    range_start += i + 1;
+  }
+}
+
+void QuicConnectionLogger::RecordAggregatePacketLossRate() const {
+  // For short connections under 22 packets in length, we'll rely on the
+  // Net.QuicSession.21CumulativePacketsReceived_* histogram to indicate packet
+  // loss rates.  This way we avoid tremendously anomalous contributions to our
+  // histogram.  (e.g., if we only got 5 packets, but lost 1, we'd otherwise
+  // record a 20% loss in this histogram!). We may still get some strange data
+  // (1 loss in 22 is still high :-/).
+  if (largest_received_packet_sequence_number_ <= 21)
+    return;
+
+  QuicPacketSequenceNumber divisor = largest_received_packet_sequence_number_;
+  QuicPacketSequenceNumber numerator = divisor - num_packets_received_;
+  if (divisor < 100000)
+    numerator *= 1000;
+  else
+    divisor /= 1000;
+  string prefix("Net.QuicSession.PacketLossRate_");
+  base::HistogramBase* histogram = base::Histogram::FactoryGet(
+      prefix + connection_description_, 1, 1000, 75,
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+  histogram->Add(numerator / divisor);
+}
+
+void QuicConnectionLogger::RecordLossHistograms() const {
+  if (largest_received_packet_sequence_number_ == 0)
+    return;  // Connection was never used.
+  RecordAggregatePacketLossRate();
+
+  base::HistogramBase* is_not_ack_histogram =
+      GetPacketSequenceNumberHistogram("IsNotAck_");
+  base::HistogramBase* is_an_ack_histogram =
+      GetPacketSequenceNumberHistogram("IsAnAck_");
+  base::HistogramBase* packet_arrived_histogram =
+      GetPacketSequenceNumberHistogram("Ack_");
+  base::HistogramBase* packet_missing_histogram =
+      GetPacketSequenceNumberHistogram("Nack_");
+  base::HistogramBase* ongoing_cumulative_packet_histogram =
+      Get21CumulativeHistogram("Some21s_");
+  base::HistogramBase* first_cumulative_packet_histogram =
+      Get21CumulativeHistogram("First21_");
+  base::HistogramBase* six_packet_histogram = Get6PacketHistogram("Some6s_");
+
+  DCHECK_EQ(received_packets_.size(), received_acks_.size());
+  const QuicPacketSequenceNumber last_index =
+      std::min<QuicPacketSequenceNumber>(received_packets_.size() - 1,
+          largest_received_packet_sequence_number_);
+  const QuicPacketSequenceNumber index_of_first_21_contribution =
+      std::min<QuicPacketSequenceNumber>(21, last_index);
+  // Bit pattern of consecutively received packets that is maintained as we scan
+  // through the received_packets_ vector. Less significant bits correspond to
+  // less recent packets, and only the low order 21 bits are ever defined.
+  // Bit is 1 iff corresponding packet was received.
+  int packet_pattern_21 = 0;
+  // Zero is an invalid packet sequence number.
+  DCHECK(!received_packets_[0]);
+  for (size_t i = 1; i <= last_index; ++i) {
+    if (received_acks_[i])
+      is_an_ack_histogram->Add(i);
+    else
+      is_not_ack_histogram->Add(i);
+
+    packet_pattern_21 >>= 1;
+    if (received_packets_[i]) {
+      packet_arrived_histogram->Add(i);
+      packet_pattern_21 |= (1 << 20);  // Turn on the 21st bit.
+    } else {
+      packet_missing_histogram->Add(i);
+    }
+
+    if (i == index_of_first_21_contribution) {
+      AddTo21CumulativeHistogram(first_cumulative_packet_histogram,
+                                 packet_pattern_21, i);
+    }
+    // We'll just record for non-overlapping ranges, to reduce histogramming
+    // cost for now.  Each call does 21 separate histogram additions.
+    if (i > 21 || i % 21 == 0) {
+      AddTo21CumulativeHistogram(ongoing_cumulative_packet_histogram,
+                                 packet_pattern_21, 21);
+    }
+
+    if (i < 6)
+      continue;  // Not enough packets to do any pattern recording.
+    int recent_6_mask = packet_pattern_21 >> 15;
+    DCHECK_LT(recent_6_mask, 64);
+    if (i == 6) {
+      Get6PacketHistogram("First6_")->Add(recent_6_mask);
+      continue;
+    }
+    // Record some overlapping patterns, to get a better picture, since this is
+    // not very expensive.
+    if (i % 3 == 0)
+      six_packet_histogram->Add(recent_6_mask);
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_connection_logger.h b/net/quic/quic_connection_logger.h
new file mode 100644
index 0000000..7693d79
--- /dev/null
+++ b/net/quic/quic_connection_logger.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_LOGGER_H_
+#define NET_QUIC_QUIC_CONNECTION_LOGGER_H_
+
+#include <bitset>
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+class QuicConnectionLoggerPeer;
+}  // namespace test
+
+class CryptoHandshakeMessage;
+class CertVerifyResult;
+
+// This class is a debug visitor of a QuicConnection which logs
+// events to |net_log|.
+class NET_EXPORT_PRIVATE QuicConnectionLogger
+    : public QuicConnectionDebugVisitor {
+ public:
+  explicit QuicConnectionLogger(const BoundNetLog& net_log);
+
+  virtual ~QuicConnectionLogger();
+
+  // QuicPacketGenerator::DebugDelegateInterface
+  virtual void OnFrameAddedToPacket(const QuicFrame& frame) OVERRIDE;
+
+  // QuicConnectionDebugVisitorInterface
+  virtual void OnPacketSent(QuicPacketSequenceNumber sequence_number,
+                            QuicPacketSequenceNumber original_sequence_number,
+                            EncryptionLevel level,
+                            TransmissionType transmission_type,
+                            const QuicEncryptedPacket& packet,
+                            WriteResult result) OVERRIDE;
+  virtual void OnPacketReceived(const IPEndPoint& self_address,
+                                const IPEndPoint& peer_address,
+                                const QuicEncryptedPacket& packet) OVERRIDE;
+  virtual void OnIncorrectConnectionId(
+      QuicConnectionId connection_id) OVERRIDE;
+  virtual void OnUndecryptablePacket() OVERRIDE;
+  virtual void OnDuplicatePacket(QuicPacketSequenceNumber sequence_number)
+      OVERRIDE;
+  virtual void OnProtocolVersionMismatch(QuicVersion version) OVERRIDE;
+  virtual void OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+  virtual void OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+  virtual void OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+  virtual void OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+  virtual void OnStopWaitingFrame(const QuicStopWaitingFrame& frame) OVERRIDE;
+  virtual void OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+  virtual void OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) OVERRIDE;
+  virtual void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) OVERRIDE;
+  virtual void OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE;
+  virtual void OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+  virtual void OnPingFrame(const QuicPingFrame& frame) OVERRIDE;
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) OVERRIDE;
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) OVERRIDE;
+  virtual void OnRevivedPacket(const QuicPacketHeader& revived_header,
+                               base::StringPiece payload) OVERRIDE;
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) OVERRIDE;
+  virtual void OnSuccessfulVersionNegotiation(
+      const QuicVersion& version) OVERRIDE;
+
+  void OnCryptoHandshakeMessageReceived(
+      const CryptoHandshakeMessage& message);
+  void OnCryptoHandshakeMessageSent(
+      const CryptoHandshakeMessage& message);
+  void UpdateReceivedFrameCounts(QuicStreamId stream_id,
+                                 int num_frames_received,
+                                 int num_duplicate_frames_received);
+  void OnCertificateVerified(const CertVerifyResult& result);
+
+ private:
+  friend class test::QuicConnectionLoggerPeer;
+
+  // Do a factory get for a histogram for recording data, about individual
+  // packet sequence numbers, that was gathered in the vectors
+  // received_packets_ and received_acks_. |statistic_name| identifies which
+  // element of data is recorded, and is used to form the histogram name.
+  base::HistogramBase* GetPacketSequenceNumberHistogram(
+      const char* statistic_name) const;
+  // Do a factory get for a histogram to record a 6-packet loss-sequence as a
+  // sample. The histogram will record the 64 distinct possible combinations.
+  // |which_6| is used to adjust the name of the histogram to distinguish the
+  // first 6 packets in a connection, vs. some later 6 packets.
+  base::HistogramBase* Get6PacketHistogram(const char* which_6) const;
+  // Do a factory get for a histogram to record cumulative stats across a 21
+  // packet sequence.  |which_21| is used to adjust the name of the histogram
+  // to distinguish the first 21 packets' loss data, vs. some later 21 packet
+  // sequences' loss data.
+  base::HistogramBase* Get21CumulativeHistogram(const char* which_21) const;
+  // Add samples associated with a |bit_mask_of_packets| to the given histogram
+  // that was provided by Get21CumulativeHistogram().  The LSB of that mask
+  // corresponds to the oldest packet sequence number in the series of packets,
+  // and the bit in the 2^20 position corresponds to the most recently received
+  // packet.  Of the maximum of 21 bits that are valid (correspond to packets),
+  // only the most significant |valid_bits_in_mask| are processed.
+  // A bit value of 0 indicates that a packet was never received, and a 1
+  // indicates the packet was received.
+  static void AddTo21CumulativeHistogram(base::HistogramBase* histogram,
+                                         int bit_mask_of_packets,
+                                         int valid_bits_in_mask);
+  // For connections longer than 21 received packets, this call will calculate
+  // the overall packet loss rate, and record it into a histogram.
+  void RecordAggregatePacketLossRate() const;
+  // At destruction time, this records results of |pacaket_received_| into
+  // histograms for specific connection types.
+  void RecordLossHistograms() const;
+
+  BoundNetLog net_log_;
+  // The last packet sequence number received.
+  QuicPacketSequenceNumber last_received_packet_sequence_number_;
+  // The size of the most recently received packet.
+  size_t last_received_packet_size_;
+  // The largest packet sequence number received.  In the case where a packet is
+  // received late (out of order), this value will not be updated.
+  QuicPacketSequenceNumber largest_received_packet_sequence_number_;
+  // The largest packet sequence number which the peer has failed to
+  // receive, according to the missing packet set in their ack frames.
+  QuicPacketSequenceNumber largest_received_missing_packet_sequence_number_;
+  // Number of times that the current received packet sequence number is
+  // smaller than the last received packet sequence number.
+  size_t num_out_of_order_received_packets_;
+  // The number of times that OnPacketHeader was called.
+  // If the network replicates packets, then this number may be slightly
+  // different from the real number of distinct packets received.
+  QuicPacketSequenceNumber num_packets_received_;
+  // Number of times a truncated ACK frame was sent.
+  size_t num_truncated_acks_sent_;
+  // Number of times a truncated ACK frame was received.
+  size_t num_truncated_acks_received_;
+  // The kCADR value provided by the server in ServerHello.
+  IPEndPoint local_address_from_shlo_;
+  // The first local address from which a packet was received.
+  IPEndPoint local_address_from_self_;
+  // Count of the number of frames received.
+  int num_frames_received_;
+  // Count of the number of duplicate frames received.
+  int num_duplicate_frames_received_;
+  // Count of the number of packets received with incorrect connection IDs.
+  int num_incorrect_connection_ids_;
+  // Count of the number of undecryptable packets received.
+  int num_undecryptable_packets_;
+  // Count of the number of duplicate packets received.
+  int num_duplicate_packets_;
+  // Vector of inital packets status' indexed by packet sequence numbers, where
+  // false means never received.  Zero is not a valid packet sequence number, so
+  // that offset is never used, and we'll track 150 packets.
+  std::bitset<151> received_packets_;
+  // Vector to indicate which of the initial 150 received packets turned out to
+  // contain solo ACK frames.  An element is true iff an ACK frame was in the
+  // corresponding packet, and there was very little else.
+  std::bitset<151> received_acks_;
+  // The available type of connection (WiFi, 3G, etc.) when connection was first
+  // used.
+  const char* const connection_description_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicConnectionLogger);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CONNECTION_LOGGER_H_
diff --git a/net/quic/quic_connection_logger_unittest.cc b/net/quic/quic_connection_logger_unittest.cc
new file mode 100644
index 0000000..9f9cd9a
--- /dev/null
+++ b/net/quic/quic_connection_logger_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection_logger.h"
+
+#include "net/quic/quic_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicConnectionLoggerPeer {
+ public:
+  static size_t num_truncated_acks_sent(const QuicConnectionLogger& logger) {
+    return logger.num_truncated_acks_sent_;
+  }
+};
+
+class QuicConnectionLoggerTest : public ::testing::Test {
+ protected:
+  QuicConnectionLoggerTest() : logger_(net_log_) {}
+
+  BoundNetLog net_log_;
+  QuicConnectionLogger logger_;
+};
+
+TEST_F(QuicConnectionLoggerTest, TruncatedAcksSentNotChanged) {
+  QuicAckFrame frame;
+  logger_.OnFrameAddedToPacket(QuicFrame(&frame));
+  EXPECT_EQ(0u, QuicConnectionLoggerPeer::num_truncated_acks_sent(logger_));
+
+  for (QuicPacketSequenceNumber i = 0; i < 256; ++i) {
+    frame.missing_packets.insert(i);
+  }
+  logger_.OnFrameAddedToPacket(QuicFrame(&frame));
+  EXPECT_EQ(0u, QuicConnectionLoggerPeer::num_truncated_acks_sent(logger_));
+}
+
+TEST_F(QuicConnectionLoggerTest, TruncatedAcksSent) {
+  QuicAckFrame frame;
+  for (QuicPacketSequenceNumber i = 0; i < 512; i += 2) {
+    frame.missing_packets.insert(i);
+  }
+  logger_.OnFrameAddedToPacket(QuicFrame(&frame));
+  EXPECT_EQ(1u, QuicConnectionLoggerPeer::num_truncated_acks_sent(logger_));
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_connection_stats.cc b/net/quic/quic_connection_stats.cc
new file mode 100644
index 0000000..7081d0d
--- /dev/null
+++ b/net/quic/quic_connection_stats.cc
@@ -0,0 +1,90 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection_stats.h"
+
+using std::ostream;
+
+namespace net {
+
+QuicConnectionStats::QuicConnectionStats()
+    : bytes_sent(0),
+      packets_sent(0),
+      stream_bytes_sent(0),
+      packets_discarded(0),
+      bytes_received(0),
+      packets_received(0),
+      packets_processed(0),
+      stream_bytes_received(0),
+      bytes_retransmitted(0),
+      packets_retransmitted(0),
+      bytes_spuriously_retransmitted(0),
+      packets_spuriously_retransmitted(0),
+      packets_lost(0),
+      slowstart_packets_lost(0),
+      packets_revived(0),
+      packets_dropped(0),
+      crypto_retransmit_count(0),
+      loss_timeout_count(0),
+      tlp_count(0),
+      rto_count(0),
+      spurious_rto_count(0),
+      min_rtt_us(0),
+      srtt_us(0),
+      max_packet_size(0),
+      estimated_bandwidth(0),
+      congestion_window(0),
+      slow_start_threshold(0),
+      packets_reordered(0),
+      max_sequence_reordering(0),
+      max_time_reordering_us(0),
+      tcp_loss_events(0),
+      cwnd_increase_congestion_avoidance(0),
+      cwnd_increase_cubic_mode(0),
+      connection_creation_time(QuicTime::Zero()) {
+}
+
+QuicConnectionStats::~QuicConnectionStats() {}
+
+ostream& operator<<(ostream& os, const QuicConnectionStats& s) {
+  os << "{ bytes sent: " << s.bytes_sent
+     << ", packets sent:" << s.packets_sent
+     << ", stream bytes sent: " << s.stream_bytes_sent
+     << ", packets discarded: " << s.packets_discarded
+     << ", bytes received: " << s.bytes_received
+     << ", packets received: " << s.packets_received
+     << ", packets processed: " << s.packets_processed
+     << ", stream bytes received: " << s.stream_bytes_received
+     << ", bytes retransmitted: " << s.bytes_retransmitted
+     << ", packets retransmitted: " << s.packets_retransmitted
+     << ", bytes spuriously retransmitted: " << s.bytes_spuriously_retransmitted
+     << ", packets spuriously retransmitted: "
+     << s.packets_spuriously_retransmitted
+     << ", packets lost: " << s.packets_lost
+     << ", slowstart packets lost: " << s.slowstart_packets_lost
+     << ", packets revived: " << s.packets_revived
+     << ", packets dropped:" << s.packets_dropped
+     << ", crypto retransmit count: " << s.crypto_retransmit_count
+     << ", tlp count: " << s.tlp_count
+     << ", rto count: " << s.rto_count
+     << ", spurious_rto_count:" << s.spurious_rto_count
+     << ", min_rtt(us): " << s.min_rtt_us
+     << ", srtt(us): " << s.srtt_us
+     << ", max packet size: " << s.max_packet_size
+     << ", estimated bandwidth: " << s.estimated_bandwidth
+     << ", congestion window: " << s.congestion_window
+     << ", slow start threshold: " << s.slow_start_threshold
+     << ", tcp_loss_events: " << s.tcp_loss_events
+     << ", packets reordered: " << s.packets_reordered
+     << ", max sequence reordering: " << s.max_sequence_reordering
+     << ", max time reordering(us): " << s.max_time_reordering_us
+     << ", total amount of cwnd increase in TCPCubic, in congestion avoidance: "
+     << s.cwnd_increase_congestion_avoidance
+     << ", amount of cwnd increase in TCPCubic, in cubic mode: "
+     << s.cwnd_increase_cubic_mode
+     << "}\n";
+  return os;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_connection_stats.h b/net/quic/quic_connection_stats.h
new file mode 100644
index 0000000..1cb8ca7
--- /dev/null
+++ b/net/quic/quic_connection_stats.h
@@ -0,0 +1,84 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_STATS_H_
+#define NET_QUIC_QUIC_CONNECTION_STATS_H_
+
+#include <ostream>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+// Structure to hold stats for a QuicConnection.
+struct NET_EXPORT_PRIVATE QuicConnectionStats {
+  QuicConnectionStats();
+  ~QuicConnectionStats();
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicConnectionStats& s);
+
+  uint64 bytes_sent;  // Includes retransmissions, fec.
+  uint32 packets_sent;
+  uint64 stream_bytes_sent;  // non-retransmitted bytes sent in a stream frame.
+  uint32 packets_discarded;  // Packets serialized and discarded before sending.
+
+  // These include version negotiation and public reset packets, which do not
+  // have sequence numbers or frame data.
+  uint64 bytes_received;  // Includes duplicate data for a stream, fec.
+  uint32 packets_received;  // Includes packets which were not processable.
+  uint32 packets_processed;  // Excludes packets which were not processable.
+  uint64 stream_bytes_received;  // Bytes received in a stream frame.
+
+  uint64 bytes_retransmitted;
+  uint32 packets_retransmitted;
+
+  uint64 bytes_spuriously_retransmitted;
+  uint32 packets_spuriously_retransmitted;
+  // Number of packets abandoned as lost by the loss detection algorithm.
+  uint32 packets_lost;
+  uint32 slowstart_packets_lost;  // Number of packets lost exiting slow start.
+
+  uint32 packets_revived;
+  uint32 packets_dropped;  // Duplicate or less than least unacked.
+  uint32 crypto_retransmit_count;
+  // Count of times the loss detection alarm fired.  At least one packet should
+  // be lost when the alarm fires.
+  uint32 loss_timeout_count;
+  uint32 tlp_count;
+  uint32 rto_count;  // Count of times the rto timer fired.
+  uint32 spurious_rto_count;
+
+  uint32 min_rtt_us;  // Minimum RTT in microseconds.
+  uint32 srtt_us;  // Smoothed RTT in microseconds.
+  uint32 max_packet_size;  // In bytes.
+  uint64 estimated_bandwidth;  // In bytes per second.
+  uint32 congestion_window;  // In bytes
+  uint32 slow_start_threshold;  // In bytes
+
+  // Reordering stats for received packets.
+  // Number of packets received out of sequence number order.
+  uint32 packets_reordered;
+  // Maximum reordering observed in sequence space.
+  uint32 max_sequence_reordering;
+  // Maximum reordering observed in microseconds
+  uint32 max_time_reordering_us;
+
+  // The following stats are used only in TcpCubicSender.
+  // The number of loss events from TCP's perspective.  Each loss event includes
+  // one or more lost packets.
+  uint32 tcp_loss_events;
+  // Total amount of cwnd increase by TCPCubic in congestion avoidance.
+  uint32 cwnd_increase_congestion_avoidance;
+  // Total amount of cwnd increase by TCPCubic in cubic mode.
+  uint32 cwnd_increase_cubic_mode;
+
+  // Creation time, as reported by the QuicClock.
+  QuicTime connection_creation_time;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CONNECTION_STATS_H_
diff --git a/net/quic/quic_connection_test.cc b/net/quic/quic_connection_test.cc
new file mode 100644
index 0000000..2869513
--- /dev/null
+++ b/net/quic/quic_connection_test.cc
@@ -0,0 +1,4017 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_connection.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "net/base/net_errors.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::map;
+using std::vector;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::ContainerEq;
+using testing::Contains;
+using testing::DoAll;
+using testing::InSequence;
+using testing::InvokeWithoutArgs;
+using testing::NiceMock;
+using testing::Ref;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const char data1[] = "foo";
+const char data2[] = "bar";
+
+const bool kFin = true;
+const bool kEntropyFlag = true;
+
+const QuicPacketEntropyHash kTestEntropyHash = 76;
+
+const int kDefaultRetransmissionTimeMs = 500;
+
+class TestReceiveAlgorithm : public ReceiveAlgorithmInterface {
+ public:
+  explicit TestReceiveAlgorithm(QuicCongestionFeedbackFrame* feedback)
+      : feedback_(feedback) {
+  }
+
+  bool GenerateCongestionFeedback(
+      QuicCongestionFeedbackFrame* congestion_feedback) {
+    if (feedback_ == nullptr) {
+      return false;
+    }
+    *congestion_feedback = *feedback_;
+    return true;
+  }
+
+  MOCK_METHOD3(RecordIncomingPacket,
+               void(QuicByteCount, QuicPacketSequenceNumber, QuicTime));
+
+ private:
+  QuicCongestionFeedbackFrame* feedback_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestReceiveAlgorithm);
+};
+
+// TaggingEncrypter appends kTagSize bytes of |tag| to the end of each message.
+class TaggingEncrypter : public QuicEncrypter {
+ public:
+  explicit TaggingEncrypter(uint8 tag)
+      : tag_(tag) {
+  }
+
+  virtual ~TaggingEncrypter() {}
+
+  // QuicEncrypter interface.
+  virtual bool SetKey(StringPiece key) OVERRIDE { return true; }
+  virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+    return true;
+  }
+
+  virtual bool Encrypt(StringPiece nonce,
+                       StringPiece associated_data,
+                       StringPiece plaintext,
+                       unsigned char* output) OVERRIDE {
+    memcpy(output, plaintext.data(), plaintext.size());
+    output += plaintext.size();
+    memset(output, tag_, kTagSize);
+    return true;
+  }
+
+  virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  StringPiece associated_data,
+                                  StringPiece plaintext) OVERRIDE {
+    const size_t len = plaintext.size() + kTagSize;
+    uint8* buffer = new uint8[len];
+    Encrypt(StringPiece(), associated_data, plaintext, buffer);
+    return new QuicData(reinterpret_cast<char*>(buffer), len, true);
+  }
+
+  virtual size_t GetKeySize() const OVERRIDE { return 0; }
+  virtual size_t GetNoncePrefixSize() const OVERRIDE { return 0; }
+
+  virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE {
+    return ciphertext_size - kTagSize;
+  }
+
+  virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE {
+    return plaintext_size + kTagSize;
+  }
+
+  virtual StringPiece GetKey() const OVERRIDE {
+    return StringPiece();
+  }
+
+  virtual StringPiece GetNoncePrefix() const OVERRIDE {
+    return StringPiece();
+  }
+
+ private:
+  enum {
+    kTagSize = 12,
+  };
+
+  const uint8 tag_;
+
+  DISALLOW_COPY_AND_ASSIGN(TaggingEncrypter);
+};
+
+// TaggingDecrypter ensures that the final kTagSize bytes of the message all
+// have the same value and then removes them.
+class TaggingDecrypter : public QuicDecrypter {
+ public:
+  virtual ~TaggingDecrypter() {}
+
+  // QuicDecrypter interface
+  virtual bool SetKey(StringPiece key) OVERRIDE { return true; }
+  virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+    return true;
+  }
+
+  virtual bool Decrypt(StringPiece nonce,
+                       StringPiece associated_data,
+                       StringPiece ciphertext,
+                       unsigned char* output,
+                       size_t* output_length) OVERRIDE {
+    if (ciphertext.size() < kTagSize) {
+      return false;
+    }
+    if (!CheckTag(ciphertext, GetTag(ciphertext))) {
+      return false;
+    }
+    *output_length = ciphertext.size() - kTagSize;
+    memcpy(output, ciphertext.data(), *output_length);
+    return true;
+  }
+
+  virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  StringPiece associated_data,
+                                  StringPiece ciphertext) OVERRIDE {
+    if (ciphertext.size() < kTagSize) {
+      return nullptr;
+    }
+    if (!CheckTag(ciphertext, GetTag(ciphertext))) {
+      return nullptr;
+    }
+    const size_t len = ciphertext.size() - kTagSize;
+    uint8* buf = new uint8[len];
+    memcpy(buf, ciphertext.data(), len);
+    return new QuicData(reinterpret_cast<char*>(buf), len,
+                        true /* owns buffer */);
+  }
+
+  virtual StringPiece GetKey() const OVERRIDE { return StringPiece(); }
+  virtual StringPiece GetNoncePrefix() const OVERRIDE { return StringPiece(); }
+
+ protected:
+  virtual uint8 GetTag(StringPiece ciphertext) {
+    return ciphertext.data()[ciphertext.size()-1];
+  }
+
+ private:
+  enum {
+    kTagSize = 12,
+  };
+
+  bool CheckTag(StringPiece ciphertext, uint8 tag) {
+    for (size_t i = ciphertext.size() - kTagSize; i < ciphertext.size(); i++) {
+      if (ciphertext.data()[i] != tag) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+// StringTaggingDecrypter ensures that the final kTagSize bytes of the message
+// match the expected value.
+class StrictTaggingDecrypter : public TaggingDecrypter {
+ public:
+  explicit StrictTaggingDecrypter(uint8 tag) : tag_(tag) {}
+  virtual ~StrictTaggingDecrypter() {}
+
+  // TaggingQuicDecrypter
+  virtual uint8 GetTag(StringPiece ciphertext) OVERRIDE {
+    return tag_;
+  }
+
+ private:
+  const uint8 tag_;
+};
+
+class TestConnectionHelper : public QuicConnectionHelperInterface {
+ public:
+  class TestAlarm : public QuicAlarm {
+   public:
+    explicit TestAlarm(QuicAlarm::Delegate* delegate)
+        : QuicAlarm(delegate) {
+    }
+
+    virtual void SetImpl() OVERRIDE {}
+    virtual void CancelImpl() OVERRIDE {}
+    using QuicAlarm::Fire;
+  };
+
+  TestConnectionHelper(MockClock* clock, MockRandom* random_generator)
+      : clock_(clock),
+        random_generator_(random_generator) {
+    clock_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+  // QuicConnectionHelperInterface
+  virtual const QuicClock* GetClock() const OVERRIDE {
+    return clock_;
+  }
+
+  virtual QuicRandom* GetRandomGenerator() OVERRIDE {
+    return random_generator_;
+  }
+
+  virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE {
+    return new TestAlarm(delegate);
+  }
+
+ private:
+  MockClock* clock_;
+  MockRandom* random_generator_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestConnectionHelper);
+};
+
+class TestPacketWriter : public QuicPacketWriter {
+ public:
+  explicit TestPacketWriter(QuicVersion version)
+      : version_(version),
+        framer_(SupportedVersions(version_)),
+        last_packet_size_(0),
+        write_blocked_(false),
+        block_on_next_write_(false),
+        is_write_blocked_data_buffered_(false),
+        final_bytes_of_last_packet_(0),
+        final_bytes_of_previous_packet_(0),
+        use_tagging_decrypter_(false),
+        packets_write_attempts_(0) {
+  }
+
+  // QuicPacketWriter interface
+  virtual WriteResult WritePacket(
+      const char* buffer, size_t buf_len,
+      const IPAddressNumber& self_address,
+      const IPEndPoint& peer_address) OVERRIDE {
+    QuicEncryptedPacket packet(buffer, buf_len);
+    ++packets_write_attempts_;
+
+    if (packet.length() >= sizeof(final_bytes_of_last_packet_)) {
+      final_bytes_of_previous_packet_ = final_bytes_of_last_packet_;
+      memcpy(&final_bytes_of_last_packet_, packet.data() + packet.length() - 4,
+             sizeof(final_bytes_of_last_packet_));
+    }
+
+    if (use_tagging_decrypter_) {
+      framer_.framer()->SetDecrypter(new TaggingDecrypter, ENCRYPTION_NONE);
+    }
+    EXPECT_TRUE(framer_.ProcessPacket(packet));
+    if (block_on_next_write_) {
+      write_blocked_ = true;
+      block_on_next_write_ = false;
+    }
+    if (IsWriteBlocked()) {
+      return WriteResult(WRITE_STATUS_BLOCKED, -1);
+    }
+    last_packet_size_ = packet.length();
+    return WriteResult(WRITE_STATUS_OK, last_packet_size_);
+  }
+
+  virtual bool IsWriteBlockedDataBuffered() const OVERRIDE {
+    return is_write_blocked_data_buffered_;
+  }
+
+  virtual bool IsWriteBlocked() const OVERRIDE { return write_blocked_; }
+
+  virtual void SetWritable() OVERRIDE { write_blocked_ = false; }
+
+  void BlockOnNextWrite() { block_on_next_write_ = true; }
+
+  const QuicPacketHeader& header() { return framer_.header(); }
+
+  size_t frame_count() const { return framer_.num_frames(); }
+
+  const vector<QuicAckFrame>& ack_frames() const {
+    return framer_.ack_frames();
+  }
+
+  const vector<QuicCongestionFeedbackFrame>& feedback_frames() const {
+    return framer_.feedback_frames();
+  }
+
+  const vector<QuicStopWaitingFrame>& stop_waiting_frames() const {
+    return framer_.stop_waiting_frames();
+  }
+
+  const vector<QuicConnectionCloseFrame>& connection_close_frames() const {
+    return framer_.connection_close_frames();
+  }
+
+  const vector<QuicStreamFrame>& stream_frames() const {
+    return framer_.stream_frames();
+  }
+
+  const vector<QuicPingFrame>& ping_frames() const {
+    return framer_.ping_frames();
+  }
+
+  size_t last_packet_size() {
+    return last_packet_size_;
+  }
+
+  const QuicVersionNegotiationPacket* version_negotiation_packet() {
+    return framer_.version_negotiation_packet();
+  }
+
+  void set_is_write_blocked_data_buffered(bool buffered) {
+    is_write_blocked_data_buffered_ = buffered;
+  }
+
+  void set_is_server(bool is_server) {
+    // We invert is_server here, because the framer needs to parse packets
+    // we send.
+    QuicFramerPeer::SetIsServer(framer_.framer(), !is_server);
+  }
+
+  // final_bytes_of_last_packet_ returns the last four bytes of the previous
+  // packet as a little-endian, uint32. This is intended to be used with a
+  // TaggingEncrypter so that tests can determine which encrypter was used for
+  // a given packet.
+  uint32 final_bytes_of_last_packet() { return final_bytes_of_last_packet_; }
+
+  // Returns the final bytes of the second to last packet.
+  uint32 final_bytes_of_previous_packet() {
+    return final_bytes_of_previous_packet_;
+  }
+
+  void use_tagging_decrypter() {
+    use_tagging_decrypter_ = true;
+  }
+
+  uint32 packets_write_attempts() { return packets_write_attempts_; }
+
+  void Reset() { framer_.Reset(); }
+
+  void SetSupportedVersions(const QuicVersionVector& versions) {
+    framer_.SetSupportedVersions(versions);
+  }
+
+ private:
+  QuicVersion version_;
+  SimpleQuicFramer framer_;
+  size_t last_packet_size_;
+  bool write_blocked_;
+  bool block_on_next_write_;
+  bool is_write_blocked_data_buffered_;
+  uint32 final_bytes_of_last_packet_;
+  uint32 final_bytes_of_previous_packet_;
+  bool use_tagging_decrypter_;
+  uint32 packets_write_attempts_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPacketWriter);
+};
+
+class TestConnection : public QuicConnection {
+ public:
+  TestConnection(QuicConnectionId connection_id,
+                 IPEndPoint address,
+                 TestConnectionHelper* helper,
+                 const PacketWriterFactory& factory,
+                 bool is_server,
+                 QuicVersion version)
+      : QuicConnection(connection_id,
+                       address,
+                       helper,
+                       factory,
+                       /* owns_writer= */ false,
+                       is_server,
+                       SupportedVersions(version)) {
+    // Disable tail loss probes for most tests.
+    QuicSentPacketManagerPeer::SetMaxTailLossProbes(
+        QuicConnectionPeer::GetSentPacketManager(this), 0);
+    writer()->set_is_server(is_server);
+  }
+
+  void SendAck() {
+    QuicConnectionPeer::SendAck(this);
+  }
+
+  void SetReceiveAlgorithm(TestReceiveAlgorithm* receive_algorithm) {
+     QuicConnectionPeer::SetReceiveAlgorithm(this, receive_algorithm);
+  }
+
+  void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+    QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+  }
+
+  void SetLossAlgorithm(LossDetectionInterface* loss_algorithm) {
+    QuicSentPacketManagerPeer::SetLossAlgorithm(
+        QuicConnectionPeer::GetSentPacketManager(this), loss_algorithm);
+  }
+
+  void SendPacket(EncryptionLevel level,
+                  QuicPacketSequenceNumber sequence_number,
+                  QuicPacket* packet,
+                  QuicPacketEntropyHash entropy_hash,
+                  HasRetransmittableData retransmittable) {
+    RetransmittableFrames* retransmittable_frames =
+        retransmittable == HAS_RETRANSMITTABLE_DATA
+            ? new RetransmittableFrames()
+            : nullptr;
+    OnSerializedPacket(
+        SerializedPacket(sequence_number, PACKET_6BYTE_SEQUENCE_NUMBER,
+                         packet, entropy_hash, retransmittable_frames));
+  }
+
+  QuicConsumedData SendStreamDataWithString(
+      QuicStreamId id,
+      StringPiece data,
+      QuicStreamOffset offset,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* delegate) {
+    return SendStreamDataWithStringHelper(id, data, offset, fin,
+                                          MAY_FEC_PROTECT, delegate);
+  }
+
+  QuicConsumedData SendStreamDataWithStringWithFec(
+      QuicStreamId id,
+      StringPiece data,
+      QuicStreamOffset offset,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* delegate) {
+    return SendStreamDataWithStringHelper(id, data, offset, fin,
+                                          MUST_FEC_PROTECT, delegate);
+  }
+
+  QuicConsumedData SendStreamDataWithStringHelper(
+      QuicStreamId id,
+      StringPiece data,
+      QuicStreamOffset offset,
+      bool fin,
+      FecProtection fec_protection,
+      QuicAckNotifier::DelegateInterface* delegate) {
+    IOVector data_iov;
+    if (!data.empty()) {
+      data_iov.Append(const_cast<char*>(data.data()), data.size());
+    }
+    return QuicConnection::SendStreamData(id, data_iov, offset, fin,
+                                          fec_protection, delegate);
+  }
+
+  QuicConsumedData SendStreamData3() {
+    return SendStreamDataWithString(kClientDataStreamId1, "food", 0, !kFin,
+                                    nullptr);
+  }
+
+  QuicConsumedData SendStreamData3WithFec() {
+    return SendStreamDataWithStringWithFec(kClientDataStreamId1, "food", 0,
+                                           !kFin, nullptr);
+  }
+
+  QuicConsumedData SendStreamData5() {
+    return SendStreamDataWithString(kClientDataStreamId2, "food2", 0, !kFin,
+                                    nullptr);
+  }
+
+  QuicConsumedData SendStreamData5WithFec() {
+    return SendStreamDataWithStringWithFec(kClientDataStreamId2, "food2", 0,
+                                           !kFin, nullptr);
+  }
+  // Ensures the connection can write stream data before writing.
+  QuicConsumedData EnsureWritableAndSendStreamData5() {
+    EXPECT_TRUE(CanWriteStreamData());
+    return SendStreamData5();
+  }
+
+  // The crypto stream has special semantics so that it is not blocked by a
+  // congestion window limitation, and also so that it gets put into a separate
+  // packet (so that it is easier to reason about a crypto frame not being
+  // split needlessly across packet boundaries).  As a result, we have separate
+  // tests for some cases for this stream.
+  QuicConsumedData SendCryptoStreamData() {
+    return SendStreamDataWithString(kCryptoStreamId, "chlo", 0, !kFin, nullptr);
+  }
+
+  bool is_server() {
+    return QuicConnectionPeer::IsServer(this);
+  }
+
+  void set_version(QuicVersion version) {
+    QuicConnectionPeer::GetFramer(this)->set_version(version);
+  }
+
+  void SetSupportedVersions(const QuicVersionVector& versions) {
+    QuicConnectionPeer::GetFramer(this)->SetSupportedVersions(versions);
+    writer()->SetSupportedVersions(versions);
+  }
+
+  void set_is_server(bool is_server) {
+    writer()->set_is_server(is_server);
+    QuicConnectionPeer::SetIsServer(this, is_server);
+  }
+
+  TestConnectionHelper::TestAlarm* GetAckAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetAckAlarm(this));
+  }
+
+  TestConnectionHelper::TestAlarm* GetPingAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetPingAlarm(this));
+  }
+
+  TestConnectionHelper::TestAlarm* GetResumeWritesAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetResumeWritesAlarm(this));
+  }
+
+  TestConnectionHelper::TestAlarm* GetRetransmissionAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetRetransmissionAlarm(this));
+  }
+
+  TestConnectionHelper::TestAlarm* GetSendAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetSendAlarm(this));
+  }
+
+  TestConnectionHelper::TestAlarm* GetTimeoutAlarm() {
+    return reinterpret_cast<TestConnectionHelper::TestAlarm*>(
+        QuicConnectionPeer::GetTimeoutAlarm(this));
+  }
+
+  using QuicConnection::SelectMutualVersion;
+
+ private:
+  TestPacketWriter* writer() {
+    return static_cast<TestPacketWriter*>(QuicConnection::writer());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(TestConnection);
+};
+
+// Used for testing packets revived from FEC packets.
+class FecQuicConnectionDebugVisitor
+    : public QuicConnectionDebugVisitor {
+ public:
+  virtual void OnRevivedPacket(const QuicPacketHeader& header,
+                               StringPiece data) OVERRIDE {
+    revived_header_ = header;
+  }
+
+  // Public accessor method.
+  QuicPacketHeader revived_header() const {
+    return revived_header_;
+  }
+
+ private:
+  QuicPacketHeader revived_header_;
+};
+
+class MockPacketWriterFactory : public QuicConnection::PacketWriterFactory {
+ public:
+  MockPacketWriterFactory(QuicPacketWriter* writer) {
+    ON_CALL(*this, Create(_)).WillByDefault(Return(writer));
+  }
+  virtual ~MockPacketWriterFactory() {}
+
+  MOCK_CONST_METHOD1(Create, QuicPacketWriter*(QuicConnection* connection));
+};
+
+class QuicConnectionTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+  QuicConnectionTest()
+      : connection_id_(42),
+        framer_(SupportedVersions(version()), QuicTime::Zero(), false),
+        peer_creator_(connection_id_, &framer_, &random_generator_),
+        send_algorithm_(new StrictMock<MockSendAlgorithm>),
+        loss_algorithm_(new MockLossAlgorithm()),
+        helper_(new TestConnectionHelper(&clock_, &random_generator_)),
+        writer_(new TestPacketWriter(version())),
+        factory_(writer_.get()),
+        connection_(connection_id_, IPEndPoint(), helper_.get(),
+                    factory_, false, version()),
+        frame1_(1, false, 0, MakeIOVector(data1)),
+        frame2_(1, false, 3, MakeIOVector(data2)),
+        sequence_number_length_(PACKET_6BYTE_SEQUENCE_NUMBER),
+        connection_id_length_(PACKET_8BYTE_CONNECTION_ID) {
+    connection_.set_visitor(&visitor_);
+    connection_.SetSendAlgorithm(send_algorithm_);
+    connection_.SetLossAlgorithm(loss_algorithm_);
+    framer_.set_received_entropy_calculator(&entropy_calculator_);
+    // Simplify tests by not sending feedback unless specifically configured.
+    SetFeedback(nullptr);
+    EXPECT_CALL(
+        *send_algorithm_, TimeUntilSend(_, _, _)).WillRepeatedly(Return(
+            QuicTime::Delta::Zero()));
+    EXPECT_CALL(*receive_algorithm_,
+                RecordIncomingPacket(_, _, _)).Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+        Return(QuicTime::Delta::Zero()));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+        Return(kMaxPacketSize));
+    ON_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .WillByDefault(Return(true));
+    EXPECT_CALL(*send_algorithm_, HasReliableBandwidthEstimate())
+        .Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, BandwidthEstimate())
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(QuicBandwidth::Zero()));
+    EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber());
+    EXPECT_CALL(visitor_, WillingAndAbleToWrite()).Times(AnyNumber());
+    EXPECT_CALL(visitor_, HasPendingHandshake()).Times(AnyNumber());
+    EXPECT_CALL(visitor_, OnCanWrite()).Times(AnyNumber());
+    EXPECT_CALL(visitor_, HasOpenDataStreams()).WillRepeatedly(Return(false));
+    EXPECT_CALL(visitor_, OnCongestionWindowChange(_)).Times(AnyNumber());
+
+    EXPECT_CALL(*loss_algorithm_, GetLossTimeout())
+        .WillRepeatedly(Return(QuicTime::Zero()));
+    EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+        .WillRepeatedly(Return(SequenceNumberSet()));
+  }
+
+  QuicVersion version() {
+    return GetParam();
+  }
+
+  QuicAckFrame* outgoing_ack() {
+    outgoing_ack_.reset(QuicConnectionPeer::CreateAckFrame(&connection_));
+    return outgoing_ack_.get();
+  }
+
+  QuicStopWaitingFrame* stop_waiting() {
+    stop_waiting_.reset(
+        QuicConnectionPeer::CreateStopWaitingFrame(&connection_));
+    return stop_waiting_.get();
+  }
+
+  QuicPacketSequenceNumber least_unacked() {
+    if (writer_->stop_waiting_frames().empty()) {
+      return 0;
+    }
+    return writer_->stop_waiting_frames()[0].least_unacked;
+  }
+
+  void use_tagging_decrypter() {
+    writer_->use_tagging_decrypter();
+  }
+
+  void ProcessPacket(QuicPacketSequenceNumber number) {
+    EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+    ProcessDataPacket(number, 0, !kEntropyFlag);
+  }
+
+  QuicPacketEntropyHash ProcessFramePacket(QuicFrame frame) {
+    QuicFrames frames;
+    frames.push_back(QuicFrame(frame));
+    QuicPacketCreatorPeer::SetSendVersionInPacket(&peer_creator_,
+                                                  connection_.is_server());
+    SerializedPacket serialized_packet =
+        peer_creator_.SerializeAllFrames(frames);
+    scoped_ptr<QuicPacket> packet(serialized_packet.packet);
+    scoped_ptr<QuicEncryptedPacket> encrypted(
+        framer_.EncryptPacket(ENCRYPTION_NONE,
+                              serialized_packet.sequence_number, *packet));
+    connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+    return serialized_packet.entropy_hash;
+  }
+
+  size_t ProcessDataPacket(QuicPacketSequenceNumber number,
+                           QuicFecGroupNumber fec_group,
+                           bool entropy_flag) {
+    return ProcessDataPacketAtLevel(number, fec_group, entropy_flag,
+                                    ENCRYPTION_NONE);
+  }
+
+  size_t ProcessDataPacketAtLevel(QuicPacketSequenceNumber number,
+                                  QuicFecGroupNumber fec_group,
+                                  bool entropy_flag,
+                                  EncryptionLevel level) {
+    scoped_ptr<QuicPacket> packet(ConstructDataPacket(number, fec_group,
+                                                      entropy_flag));
+    scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+        level, number, *packet));
+    connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+    return encrypted->length();
+  }
+
+  void ProcessPingPacket(QuicPacketSequenceNumber number) {
+    scoped_ptr<QuicPacket> packet(ConstructPingPacket(number));
+    scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+        ENCRYPTION_NONE, number, *packet));
+    connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+  }
+
+  void ProcessClosePacket(QuicPacketSequenceNumber number,
+                          QuicFecGroupNumber fec_group) {
+    scoped_ptr<QuicPacket> packet(ConstructClosePacket(number, fec_group));
+    scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+        ENCRYPTION_NONE, number, *packet));
+    connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+  }
+
+  size_t ProcessFecProtectedPacket(QuicPacketSequenceNumber number,
+                                   bool expect_revival, bool entropy_flag) {
+    if (expect_revival) {
+      EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+    }
+    EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1).
+          RetiresOnSaturation();
+    return ProcessDataPacket(number, 1, entropy_flag);
+  }
+
+  // Processes an FEC packet that covers the packets that would have been
+  // received.
+  size_t ProcessFecPacket(QuicPacketSequenceNumber number,
+                          QuicPacketSequenceNumber min_protected_packet,
+                          bool expect_revival,
+                          bool entropy_flag,
+                          QuicPacket* packet) {
+    if (expect_revival) {
+      EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+    }
+
+    // Construct the decrypted data packet so we can compute the correct
+    // redundancy. If |packet| has been provided then use that, otherwise
+    // construct a default data packet.
+    scoped_ptr<QuicPacket> data_packet;
+    if (packet) {
+      data_packet.reset(packet);
+    } else {
+      data_packet.reset(ConstructDataPacket(number, 1, !kEntropyFlag));
+    }
+
+    header_.public_header.connection_id = connection_id_;
+    header_.public_header.reset_flag = false;
+    header_.public_header.version_flag = false;
+    header_.public_header.sequence_number_length = sequence_number_length_;
+    header_.public_header.connection_id_length = connection_id_length_;
+    header_.packet_sequence_number = number;
+    header_.entropy_flag = entropy_flag;
+    header_.fec_flag = true;
+    header_.is_in_fec_group = IN_FEC_GROUP;
+    header_.fec_group = min_protected_packet;
+    QuicFecData fec_data;
+    fec_data.fec_group = header_.fec_group;
+
+    // Since all data packets in this test have the same payload, the
+    // redundancy is either equal to that payload or the xor of that payload
+    // with itself, depending on the number of packets.
+    if (((number - min_protected_packet) % 2) == 0) {
+      for (size_t i = GetStartOfFecProtectedData(
+               header_.public_header.connection_id_length,
+               header_.public_header.version_flag,
+               header_.public_header.sequence_number_length);
+           i < data_packet->length(); ++i) {
+        data_packet->mutable_data()[i] ^= data_packet->data()[i];
+      }
+    }
+    fec_data.redundancy = data_packet->FecProtectedData();
+
+    scoped_ptr<QuicPacket> fec_packet(
+        framer_.BuildFecPacket(header_, fec_data).packet);
+    scoped_ptr<QuicEncryptedPacket> encrypted(
+        framer_.EncryptPacket(ENCRYPTION_NONE, number, *fec_packet));
+
+    connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+    return encrypted->length();
+  }
+
+  QuicByteCount SendStreamDataToPeer(QuicStreamId id,
+                                     StringPiece data,
+                                     QuicStreamOffset offset,
+                                     bool fin,
+                                     QuicPacketSequenceNumber* last_packet) {
+    QuicByteCount packet_size;
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .WillOnce(DoAll(SaveArg<3>(&packet_size), Return(true)));
+    connection_.SendStreamDataWithString(id, data, offset, fin, nullptr);
+    if (last_packet != nullptr) {
+      *last_packet =
+          QuicConnectionPeer::GetPacketCreator(&connection_)->sequence_number();
+    }
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .Times(AnyNumber());
+    return packet_size;
+  }
+
+  void SendAckPacketToPeer() {
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+    connection_.SendAck();
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .Times(AnyNumber());
+  }
+
+  QuicPacketEntropyHash ProcessAckPacket(QuicAckFrame* frame) {
+    return ProcessFramePacket(QuicFrame(frame));
+  }
+
+  QuicPacketEntropyHash ProcessStopWaitingPacket(QuicStopWaitingFrame* frame) {
+    return ProcessFramePacket(QuicFrame(frame));
+  }
+
+  QuicPacketEntropyHash ProcessGoAwayPacket(QuicGoAwayFrame* frame) {
+    return ProcessFramePacket(QuicFrame(frame));
+  }
+
+  bool IsMissing(QuicPacketSequenceNumber number) {
+    return IsAwaitingPacket(*outgoing_ack(), number);
+  }
+
+  QuicPacket* ConstructDataPacket(QuicPacketSequenceNumber number,
+                                  QuicFecGroupNumber fec_group,
+                                  bool entropy_flag) {
+    header_.public_header.connection_id = connection_id_;
+    header_.public_header.reset_flag = false;
+    header_.public_header.version_flag = false;
+    header_.public_header.sequence_number_length = sequence_number_length_;
+    header_.public_header.connection_id_length = connection_id_length_;
+    header_.entropy_flag = entropy_flag;
+    header_.fec_flag = false;
+    header_.packet_sequence_number = number;
+    header_.is_in_fec_group = fec_group == 0u ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+    header_.fec_group = fec_group;
+
+    QuicFrames frames;
+    QuicFrame frame(&frame1_);
+    frames.push_back(frame);
+    QuicPacket* packet =
+        BuildUnsizedDataPacket(&framer_, header_, frames).packet;
+    EXPECT_TRUE(packet != nullptr);
+    return packet;
+  }
+
+  QuicPacket* ConstructPingPacket(QuicPacketSequenceNumber number) {
+    header_.public_header.connection_id = connection_id_;
+    header_.packet_sequence_number = number;
+    header_.public_header.reset_flag = false;
+    header_.public_header.version_flag = false;
+    header_.entropy_flag = false;
+    header_.fec_flag = false;
+    header_.is_in_fec_group = NOT_IN_FEC_GROUP;
+    header_.fec_group = 0;
+
+    QuicPingFrame ping;
+
+    QuicFrames frames;
+    QuicFrame frame(&ping);
+    frames.push_back(frame);
+    QuicPacket* packet =
+        BuildUnsizedDataPacket(&framer_, header_, frames).packet;
+    EXPECT_TRUE(packet != nullptr);
+    return packet;
+  }
+
+  QuicPacket* ConstructClosePacket(QuicPacketSequenceNumber number,
+                                   QuicFecGroupNumber fec_group) {
+    header_.public_header.connection_id = connection_id_;
+    header_.packet_sequence_number = number;
+    header_.public_header.reset_flag = false;
+    header_.public_header.version_flag = false;
+    header_.entropy_flag = false;
+    header_.fec_flag = false;
+    header_.is_in_fec_group = fec_group == 0u ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+    header_.fec_group = fec_group;
+
+    QuicConnectionCloseFrame qccf;
+    qccf.error_code = QUIC_PEER_GOING_AWAY;
+
+    QuicFrames frames;
+    QuicFrame frame(&qccf);
+    frames.push_back(frame);
+    QuicPacket* packet =
+        BuildUnsizedDataPacket(&framer_, header_, frames).packet;
+    EXPECT_TRUE(packet != nullptr);
+    return packet;
+  }
+
+  void SetFeedback(QuicCongestionFeedbackFrame* feedback) {
+    receive_algorithm_ = new TestReceiveAlgorithm(feedback);
+    connection_.SetReceiveAlgorithm(receive_algorithm_);
+  }
+
+  QuicTime::Delta DefaultRetransmissionTime() {
+    return QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+  }
+
+  QuicTime::Delta DefaultDelayedAckTime() {
+    return QuicTime::Delta::FromMilliseconds(kMaxDelayedAckTimeMs);
+  }
+
+  // Initialize a frame acknowledging all packets up to largest_observed.
+  const QuicAckFrame InitAckFrame(QuicPacketSequenceNumber largest_observed) {
+    QuicAckFrame frame(MakeAckFrame(largest_observed));
+    if (largest_observed > 0) {
+      frame.entropy_hash =
+          QuicConnectionPeer::GetSentEntropyHash(&connection_,
+                                                 largest_observed);
+    }
+    return frame;
+  }
+
+  const QuicStopWaitingFrame InitStopWaitingFrame(
+      QuicPacketSequenceNumber least_unacked) {
+    QuicStopWaitingFrame frame;
+    frame.least_unacked = least_unacked;
+    return frame;
+  }
+
+  // Explicitly nack a packet.
+  void NackPacket(QuicPacketSequenceNumber missing, QuicAckFrame* frame) {
+    frame->missing_packets.insert(missing);
+    frame->entropy_hash ^=
+        QuicConnectionPeer::PacketEntropy(&connection_, missing);
+  }
+
+  // Undo nacking a packet within the frame.
+  void AckPacket(QuicPacketSequenceNumber arrived, QuicAckFrame* frame) {
+    EXPECT_THAT(frame->missing_packets, Contains(arrived));
+    frame->missing_packets.erase(arrived);
+    frame->entropy_hash ^=
+        QuicConnectionPeer::PacketEntropy(&connection_, arrived);
+  }
+
+  void TriggerConnectionClose() {
+    // Send an erroneous packet to close the connection.
+    EXPECT_CALL(visitor_,
+                OnConnectionClosed(QUIC_INVALID_PACKET_HEADER, false));
+    // Call ProcessDataPacket rather than ProcessPacket, as we should not get a
+    // packet call to the visitor.
+    ProcessDataPacket(6000, 0, !kEntropyFlag);
+    EXPECT_FALSE(QuicConnectionPeer::GetConnectionClosePacket(&connection_) ==
+                 nullptr);
+  }
+
+  void BlockOnNextWrite() {
+    writer_->BlockOnNextWrite();
+    EXPECT_CALL(visitor_, OnWriteBlocked()).Times(AtLeast(1));
+  }
+
+  void CongestionBlockWrites() {
+    EXPECT_CALL(*send_algorithm_,
+                TimeUntilSend(_, _, _)).WillRepeatedly(
+                    testing::Return(QuicTime::Delta::FromSeconds(1)));
+  }
+
+  void CongestionUnblockWrites() {
+    EXPECT_CALL(*send_algorithm_,
+                TimeUntilSend(_, _, _)).WillRepeatedly(
+                    testing::Return(QuicTime::Delta::Zero()));
+  }
+
+  QuicConnectionId connection_id_;
+  QuicFramer framer_;
+  QuicPacketCreator peer_creator_;
+  MockEntropyCalculator entropy_calculator_;
+
+  MockSendAlgorithm* send_algorithm_;
+  MockLossAlgorithm* loss_algorithm_;
+  TestReceiveAlgorithm* receive_algorithm_;
+  MockClock clock_;
+  MockRandom random_generator_;
+  scoped_ptr<TestConnectionHelper> helper_;
+  scoped_ptr<TestPacketWriter> writer_;
+  NiceMock<MockPacketWriterFactory> factory_;
+  TestConnection connection_;
+  StrictMock<MockConnectionVisitor> visitor_;
+
+  QuicPacketHeader header_;
+  QuicStreamFrame frame1_;
+  QuicStreamFrame frame2_;
+  scoped_ptr<QuicAckFrame> outgoing_ack_;
+  scoped_ptr<QuicStopWaitingFrame> stop_waiting_;
+  QuicSequenceNumberLength sequence_number_length_;
+  QuicConnectionIdLength connection_id_length_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicConnectionTest);
+};
+
+// Run all end to end tests with all supported versions.
+INSTANTIATE_TEST_CASE_P(SupportedVersion,
+                        QuicConnectionTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicConnectionTest, PacketsInOrder) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(1);
+  EXPECT_EQ(1u, outgoing_ack()->largest_observed);
+  EXPECT_EQ(0u, outgoing_ack()->missing_packets.size());
+
+  ProcessPacket(2);
+  EXPECT_EQ(2u, outgoing_ack()->largest_observed);
+  EXPECT_EQ(0u, outgoing_ack()->missing_packets.size());
+
+  ProcessPacket(3);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_EQ(0u, outgoing_ack()->missing_packets.size());
+}
+
+TEST_P(QuicConnectionTest, PacketsOutOfOrder) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(3);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(2));
+  EXPECT_TRUE(IsMissing(1));
+
+  ProcessPacket(2);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_FALSE(IsMissing(2));
+  EXPECT_TRUE(IsMissing(1));
+
+  ProcessPacket(1);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_FALSE(IsMissing(2));
+  EXPECT_FALSE(IsMissing(1));
+}
+
+TEST_P(QuicConnectionTest, DuplicatePacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(3);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(2));
+  EXPECT_TRUE(IsMissing(1));
+
+  // Send packet 3 again, but do not set the expectation that
+  // the visitor OnStreamFrames() will be called.
+  ProcessDataPacket(3, 0, !kEntropyFlag);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(2));
+  EXPECT_TRUE(IsMissing(1));
+}
+
+TEST_P(QuicConnectionTest, PacketsOutOfOrderWithAdditionsAndLeastAwaiting) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(3);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(2));
+  EXPECT_TRUE(IsMissing(1));
+
+  ProcessPacket(2);
+  EXPECT_EQ(3u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(1));
+
+  ProcessPacket(5);
+  EXPECT_EQ(5u, outgoing_ack()->largest_observed);
+  EXPECT_TRUE(IsMissing(1));
+  EXPECT_TRUE(IsMissing(4));
+
+  // Pretend at this point the client has gotten acks for 2 and 3 and 1 is a
+  // packet the peer will not retransmit.  It indicates this by sending 'least
+  // awaiting' is 4.  The connection should then realize 1 will not be
+  // retransmitted, and will remove it from the missing list.
+  peer_creator_.set_sequence_number(5);
+  QuicAckFrame frame = InitAckFrame(1);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _));
+  ProcessAckPacket(&frame);
+
+  // Force an ack to be sent.
+  SendAckPacketToPeer();
+  EXPECT_TRUE(IsMissing(4));
+}
+
+TEST_P(QuicConnectionTest, RejectPacketTooFarOut) {
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(QUIC_INVALID_PACKET_HEADER, false));
+  // Call ProcessDataPacket rather than ProcessPacket, as we should not get a
+  // packet call to the visitor.
+  ProcessDataPacket(6000, 0, !kEntropyFlag);
+  EXPECT_FALSE(QuicConnectionPeer::GetConnectionClosePacket(&connection_) ==
+               nullptr);
+}
+
+TEST_P(QuicConnectionTest, RejectUnencryptedStreamData) {
+  // Process an unencrypted packet from the non-crypto stream.
+  frame1_.stream_id = 3;
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_UNENCRYPTED_STREAM_DATA,
+                                           false));
+  ProcessDataPacket(1, 0, !kEntropyFlag);
+  EXPECT_FALSE(QuicConnectionPeer::GetConnectionClosePacket(&connection_) ==
+               nullptr);
+  const vector<QuicConnectionCloseFrame>& connection_close_frames =
+      writer_->connection_close_frames();
+  EXPECT_EQ(1u, connection_close_frames.size());
+  EXPECT_EQ(QUIC_UNENCRYPTED_STREAM_DATA,
+            connection_close_frames[0].error_code);
+}
+
+TEST_P(QuicConnectionTest, TruncatedAck) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicPacketSequenceNumber num_packets = 256 * 2 + 1;
+  for (QuicPacketSequenceNumber i = 0; i < num_packets; ++i) {
+    SendStreamDataToPeer(3, "foo", i * 3, !kFin, nullptr);
+  }
+
+  QuicAckFrame frame = InitAckFrame(num_packets);
+  SequenceNumberSet lost_packets;
+  // Create an ack with 256 nacks, none adjacent to one another.
+  for (QuicPacketSequenceNumber i = 1; i <= 256; ++i) {
+    NackPacket(i * 2, &frame);
+    if (i < 256) {  // Last packet is nacked, but not lost.
+      lost_packets.insert(i * 2);
+    }
+  }
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(entropy_calculator_,
+              EntropyHash(511)).WillOnce(testing::Return(0));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&frame);
+
+  const QuicSentPacketManager& sent_packet_manager =
+      connection_.sent_packet_manager();
+  // A truncated ack will not have the true largest observed.
+  EXPECT_GT(num_packets, sent_packet_manager.largest_observed());
+
+  AckPacket(192, &frame);
+
+  // Removing one missing packet allows us to ack 192 and one more range, but
+  // 192 has already been declared lost, so it doesn't register as an ack.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&frame);
+  EXPECT_EQ(num_packets, sent_packet_manager.largest_observed());
+}
+
+TEST_P(QuicConnectionTest, AckReceiptCausesAckSendBadEntropy) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(1);
+  // Delay sending, then queue up an ack.
+  EXPECT_CALL(*send_algorithm_,
+              TimeUntilSend(_, _, _)).WillOnce(
+                  testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+  QuicConnectionPeer::SendAck(&connection_);
+
+  // Process an ack with a least unacked of the received ack.
+  // This causes an ack to be sent when TimeUntilSend returns 0.
+  EXPECT_CALL(*send_algorithm_,
+              TimeUntilSend(_, _, _)).WillRepeatedly(
+                  testing::Return(QuicTime::Delta::Zero()));
+  // Skip a packet and then record an ack.
+  peer_creator_.set_sequence_number(2);
+  QuicAckFrame frame = InitAckFrame(0);
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, OutOfOrderReceiptCausesAckSend) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessPacket(3);
+  // Should ack immediately since we have missing packets.
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+
+  ProcessPacket(2);
+  // Should ack immediately since we have missing packets.
+  EXPECT_EQ(2u, writer_->packets_write_attempts());
+
+  ProcessPacket(1);
+  // Should ack immediately, since this fills the last hole.
+  EXPECT_EQ(3u, writer_->packets_write_attempts());
+
+  ProcessPacket(4);
+  // Should not cause an ack.
+  EXPECT_EQ(3u, writer_->packets_write_attempts());
+}
+
+TEST_P(QuicConnectionTest, AckReceiptCausesAckSend) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  QuicPacketSequenceNumber original;
+  QuicByteCount packet_size;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<2>(&original), SaveArg<3>(&packet_size),
+                      Return(true)));
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+  QuicAckFrame frame = InitAckFrame(original);
+  NackPacket(original, &frame);
+  // First nack triggers early retransmit.
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(1);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicPacketSequenceNumber retransmission;
+  EXPECT_CALL(*send_algorithm_,
+              OnPacketSent(_, _, _, packet_size - kQuicVersionSize, _))
+      .WillOnce(DoAll(SaveArg<2>(&retransmission), Return(true)));
+
+  ProcessAckPacket(&frame);
+
+  QuicAckFrame frame2 = InitAckFrame(retransmission);
+  NackPacket(original, &frame2);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  ProcessAckPacket(&frame2);
+
+  // Now if the peer sends an ack which still reports the retransmitted packet
+  // as missing, that will bundle an ack with data after two acks in a row
+  // indicate the high water mark needs to be raised.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _,
+                                             HAS_RETRANSMITTABLE_DATA));
+  connection_.SendStreamDataWithString(3, "foo", 3, !kFin, nullptr);
+  // No ack sent.
+  EXPECT_EQ(1u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+
+  // No more packet loss for the rest of the test.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillRepeatedly(Return(SequenceNumberSet()));
+  ProcessAckPacket(&frame2);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _,
+                                             HAS_RETRANSMITTABLE_DATA));
+  connection_.SendStreamDataWithString(3, "foo", 3, !kFin, nullptr);
+  // Ack bundled.
+  EXPECT_EQ(3u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+
+  // But an ack with no missing packets will not send an ack.
+  AckPacket(original, &frame2);
+  ProcessAckPacket(&frame2);
+  ProcessAckPacket(&frame2);
+}
+
+TEST_P(QuicConnectionTest, 20AcksCausesAckSend) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  SendStreamDataToPeer(1, "foo", 0, !kFin, nullptr);
+
+  QuicAlarm* ack_alarm = QuicConnectionPeer::GetAckAlarm(&connection_);
+  // But an ack with no missing packets will not send an ack.
+  QuicAckFrame frame = InitAckFrame(1);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillRepeatedly(Return(SequenceNumberSet()));
+  for (int i = 0; i < 20; ++i) {
+    EXPECT_FALSE(ack_alarm->IsSet());
+    ProcessAckPacket(&frame);
+  }
+  EXPECT_TRUE(ack_alarm->IsSet());
+}
+
+TEST_P(QuicConnectionTest, LeastUnackedLower) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  SendStreamDataToPeer(1, "foo", 0, !kFin, nullptr);
+  SendStreamDataToPeer(1, "bar", 3, !kFin, nullptr);
+  SendStreamDataToPeer(1, "eep", 6, !kFin, nullptr);
+
+  // Start out saying the least unacked is 2.
+  peer_creator_.set_sequence_number(5);
+  QuicStopWaitingFrame frame = InitStopWaitingFrame(2);
+  ProcessStopWaitingPacket(&frame);
+
+  // Change it to 1, but lower the sequence number to fake out-of-order packets.
+  // This should be fine.
+  peer_creator_.set_sequence_number(1);
+  // The scheduler will not process out of order acks, but all packet processing
+  // causes the connection to try to write.
+  EXPECT_CALL(visitor_, OnCanWrite());
+  QuicStopWaitingFrame frame2 = InitStopWaitingFrame(1);
+  ProcessStopWaitingPacket(&frame2);
+
+  // Now claim it's one, but set the ordering so it was sent "after" the first
+  // one.  This should cause a connection error.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  peer_creator_.set_sequence_number(7);
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(QUIC_INVALID_STOP_WAITING_DATA, false));
+  QuicStopWaitingFrame frame3 = InitStopWaitingFrame(1);
+  ProcessStopWaitingPacket(&frame3);
+}
+
+TEST_P(QuicConnectionTest, LargestObservedLower) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  SendStreamDataToPeer(1, "foo", 0, !kFin, nullptr);
+  SendStreamDataToPeer(1, "bar", 3, !kFin, nullptr);
+  SendStreamDataToPeer(1, "eep", 6, !kFin, nullptr);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+
+  // Start out saying the largest observed is 2.
+  QuicAckFrame frame1 = InitAckFrame(1);
+  QuicAckFrame frame2 = InitAckFrame(2);
+  ProcessAckPacket(&frame2);
+
+  // Now change it to 1, and it should cause a connection error.
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_INVALID_ACK_DATA, false));
+  EXPECT_CALL(visitor_, OnCanWrite()).Times(0);
+  ProcessAckPacket(&frame1);
+}
+
+TEST_P(QuicConnectionTest, AckUnsentData) {
+  // Ack a packet which has not been sent.
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_INVALID_ACK_DATA, false));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  QuicAckFrame frame(MakeAckFrame(1));
+  EXPECT_CALL(visitor_, OnCanWrite()).Times(0);
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, AckAll) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+
+  peer_creator_.set_sequence_number(1);
+  QuicAckFrame frame1 = InitAckFrame(0);
+  ProcessAckPacket(&frame1);
+}
+
+TEST_P(QuicConnectionTest, SendingDifferentSequenceNumberLengthsBandwidth) {
+  QuicPacketSequenceNumber last_packet;
+  QuicPacketCreator* creator =
+      QuicConnectionPeer::GetPacketCreator(&connection_);
+  SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet);
+  EXPECT_EQ(1u, last_packet);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+      Return(kMaxPacketSize * 256));
+
+  SendStreamDataToPeer(1, "bar", 3, !kFin, &last_packet);
+  EXPECT_EQ(2u, last_packet);
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  // The 1 packet lag is due to the sequence number length being recalculated in
+  // QuicConnection after a packet is sent.
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+      Return(kMaxPacketSize * 256 * 256));
+
+  SendStreamDataToPeer(1, "foo", 6, !kFin, &last_packet);
+  EXPECT_EQ(3u, last_packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+      Return(kMaxPacketSize * 256 * 256 * 256));
+
+  SendStreamDataToPeer(1, "bar", 9, !kFin, &last_packet);
+  EXPECT_EQ(4u, last_packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+      Return(kMaxPacketSize * 256 * 256 * 256 * 256));
+
+  SendStreamDataToPeer(1, "foo", 12, !kFin, &last_packet);
+  EXPECT_EQ(5u, last_packet);
+  EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+}
+
+// TODO(ianswett): Re-enable this test by finding a good way to test different
+// sequence number lengths without sending packets with giant gaps.
+TEST_P(QuicConnectionTest,
+       DISABLED_SendingDifferentSequenceNumberLengthsUnackedDelta) {
+  QuicPacketSequenceNumber last_packet;
+  QuicPacketCreator* creator =
+      QuicConnectionPeer::GetPacketCreator(&connection_);
+  SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet);
+  EXPECT_EQ(1u, last_packet);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  creator->set_sequence_number(100);
+
+  SendStreamDataToPeer(1, "bar", 3, !kFin, &last_packet);
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  creator->set_sequence_number(100 * 256);
+
+  SendStreamDataToPeer(1, "foo", 6, !kFin, &last_packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  creator->set_sequence_number(100 * 256 * 256);
+
+  SendStreamDataToPeer(1, "bar", 9, !kFin, &last_packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+
+  creator->set_sequence_number(100 * 256 * 256 * 256);
+
+  SendStreamDataToPeer(1, "foo", 12, !kFin, &last_packet);
+  EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER,
+            creator->next_sequence_number_length());
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            writer_->header().public_header.sequence_number_length);
+}
+
+TEST_P(QuicConnectionTest, BasicSending) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicPacketSequenceNumber last_packet;
+  SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet);  // Packet 1
+  EXPECT_EQ(1u, last_packet);
+  SendAckPacketToPeer();  // Packet 2
+
+  EXPECT_EQ(1u, least_unacked());
+
+  SendAckPacketToPeer();  // Packet 3
+  EXPECT_EQ(1u, least_unacked());
+
+  SendStreamDataToPeer(1, "bar", 3, !kFin, &last_packet);  // Packet 4
+  EXPECT_EQ(4u, last_packet);
+  SendAckPacketToPeer();  // Packet 5
+  EXPECT_EQ(1u, least_unacked());
+
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+
+  // Peer acks up to packet 3.
+  QuicAckFrame frame = InitAckFrame(3);
+  ProcessAckPacket(&frame);
+  SendAckPacketToPeer();  // Packet 6
+
+  // As soon as we've acked one, we skip ack packets 2 and 3 and note lack of
+  // ack for 4.
+  EXPECT_EQ(4u, least_unacked());
+
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+
+  // Peer acks up to packet 4, the last packet.
+  QuicAckFrame frame2 = InitAckFrame(6);
+  ProcessAckPacket(&frame2);  // Acks don't instigate acks.
+
+  // Verify that we did not send an ack.
+  EXPECT_EQ(6u, writer_->header().packet_sequence_number);
+
+  // So the last ack has not changed.
+  EXPECT_EQ(4u, least_unacked());
+
+  // If we force an ack, we shouldn't change our retransmit state.
+  SendAckPacketToPeer();  // Packet 7
+  EXPECT_EQ(7u, least_unacked());
+
+  // But if we send more data it should.
+  SendStreamDataToPeer(1, "eep", 6, !kFin, &last_packet);  // Packet 8
+  EXPECT_EQ(8u, last_packet);
+  SendAckPacketToPeer();  // Packet 9
+  EXPECT_EQ(7u, least_unacked());
+}
+
+TEST_P(QuicConnectionTest, FECSending) {
+  // All packets carry version info till version is negotiated.
+  QuicPacketCreator* creator =
+      QuicConnectionPeer::GetPacketCreator(&connection_);
+  size_t payload_length;
+  // GetPacketLengthForOneStream() assumes a stream offset of 0 in determining
+  // packet length. The size of the offset field in a stream frame is 0 for
+  // offset 0, and 2 for non-zero offsets up through 64K. Increase
+  // max_packet_length by 2 so that subsequent packets containing subsequent
+  // stream frames with non-zero offets will fit within the packet length.
+  size_t length = 2 + GetPacketLengthForOneStream(
+          connection_.version(), kIncludeVersion, PACKET_1BYTE_SEQUENCE_NUMBER,
+          IN_FEC_GROUP, &payload_length);
+  creator->set_max_packet_length(length);
+
+  // Send 4 protected data packets, which should also trigger 1 FEC packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(5);
+  // The first stream frame will have 2 fewer overhead bytes than the other 3.
+  const string payload(payload_length * 4 + 2, 'a');
+  connection_.SendStreamDataWithStringWithFec(1, payload, 0, !kFin, nullptr);
+  // Expect the FEC group to be closed after SendStreamDataWithString.
+  EXPECT_FALSE(creator->IsFecGroupOpen());
+  EXPECT_FALSE(creator->IsFecProtected());
+}
+
+TEST_P(QuicConnectionTest, FECQueueing) {
+  // All packets carry version info till version is negotiated.
+  size_t payload_length;
+  QuicPacketCreator* creator =
+      QuicConnectionPeer::GetPacketCreator(&connection_);
+  size_t length = GetPacketLengthForOneStream(
+      connection_.version(), kIncludeVersion, PACKET_1BYTE_SEQUENCE_NUMBER,
+      IN_FEC_GROUP, &payload_length);
+  creator->set_max_packet_length(length);
+  EXPECT_TRUE(creator->IsFecEnabled());
+
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  BlockOnNextWrite();
+  const string payload(payload_length, 'a');
+  connection_.SendStreamDataWithStringWithFec(1, payload, 0, !kFin, nullptr);
+  EXPECT_FALSE(creator->IsFecGroupOpen());
+  EXPECT_FALSE(creator->IsFecProtected());
+  // Expect the first data packet and the fec packet to be queued.
+  EXPECT_EQ(2u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, AbandonFECFromCongestionWindow) {
+  EXPECT_TRUE(QuicConnectionPeer::GetPacketCreator(
+      &connection_)->IsFecEnabled());
+
+  // 1 Data and 1 FEC packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 0, !kFin, nullptr);
+
+  const QuicTime::Delta retransmission_time =
+      QuicTime::Delta::FromMilliseconds(5000);
+  clock_.AdvanceTime(retransmission_time);
+
+  // Abandon FEC packet and data packet.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+  EXPECT_CALL(visitor_, OnCanWrite());
+  connection_.OnRetransmissionTimeout();
+}
+
+TEST_P(QuicConnectionTest, DontAbandonAckedFEC) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_TRUE(QuicConnectionPeer::GetPacketCreator(
+      &connection_)->IsFecEnabled());
+
+  // 1 Data and 1 FEC packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(6);
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 0, !kFin, nullptr);
+  // Send some more data afterwards to ensure early retransmit doesn't trigger.
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 3, !kFin, nullptr);
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 6, !kFin, nullptr);
+
+  QuicAckFrame ack_fec = InitAckFrame(2);
+  // Data packet missing.
+  // TODO(ianswett): Note that this is not a sensible ack, since if the FEC was
+  // received, it would cause the covered packet to be acked as well.
+  NackPacket(1, &ack_fec);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&ack_fec);
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+
+  // Don't abandon the acked FEC packet, but it will abandon 2 the subsequent
+  // FEC packets.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(3);
+  connection_.GetRetransmissionAlarm()->Fire();
+}
+
+TEST_P(QuicConnectionTest, AbandonAllFEC) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_TRUE(QuicConnectionPeer::GetPacketCreator(
+      &connection_)->IsFecEnabled());
+
+  // 1 Data and 1 FEC packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(6);
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 0, !kFin, nullptr);
+  // Send some more data afterwards to ensure early retransmit doesn't trigger.
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 3, !kFin, nullptr);
+  // Advance the time so not all the FEC packets are abandoned.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+  connection_.SendStreamDataWithStringWithFec(3, "foo", 6, !kFin, nullptr);
+
+  QuicAckFrame ack_fec = InitAckFrame(5);
+  // Ack all data packets, but no fec packets.
+  NackPacket(2, &ack_fec);
+  NackPacket(4, &ack_fec);
+
+  // Lose the first FEC packet and ack the three data packets.
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(2);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&ack_fec);
+
+  clock_.AdvanceTime(DefaultRetransmissionTime().Subtract(
+      QuicTime::Delta::FromMilliseconds(1)));
+
+  // Abandon all packets
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(false));
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Ensure the alarm is not set since all packets have been abandoned.
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, FramePacking) {
+  CongestionBlockWrites();
+
+  // Send an ack and two stream frames in 1 packet by queueing them.
+  connection_.SendAck();
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData3)),
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData5))));
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+  CongestionUnblockWrites();
+  connection_.GetSendAlarm()->Fire();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's an ack and two stream frames from
+  // two different streams.
+  EXPECT_EQ(4u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  ASSERT_EQ(2u, writer_->stream_frames().size());
+  EXPECT_EQ(kClientDataStreamId1, writer_->stream_frames()[0].stream_id);
+  EXPECT_EQ(kClientDataStreamId2, writer_->stream_frames()[1].stream_id);
+}
+
+TEST_P(QuicConnectionTest, FramePackingNonCryptoThenCrypto) {
+  CongestionBlockWrites();
+
+  // Send an ack and two stream frames (one non-crypto, then one crypto) in 2
+  // packets by queueing them.
+  connection_.SendAck();
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData3)),
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendCryptoStreamData))));
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+  CongestionUnblockWrites();
+  connection_.GetSendAlarm()->Fire();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's the crypto stream frame.
+  EXPECT_EQ(1u, writer_->frame_count());
+  ASSERT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_EQ(kCryptoStreamId, writer_->stream_frames()[0].stream_id);
+}
+
+TEST_P(QuicConnectionTest, FramePackingCryptoThenNonCrypto) {
+  CongestionBlockWrites();
+
+  // Send an ack and two stream frames (one crypto, then one non-crypto) in 2
+  // packets by queueing them.
+  connection_.SendAck();
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendCryptoStreamData)),
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData3))));
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+  CongestionUnblockWrites();
+  connection_.GetSendAlarm()->Fire();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's the stream frame from stream 3.
+  EXPECT_EQ(1u, writer_->frame_count());
+  ASSERT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_EQ(kClientDataStreamId1, writer_->stream_frames()[0].stream_id);
+}
+
+TEST_P(QuicConnectionTest, FramePackingFEC) {
+  EXPECT_TRUE(QuicConnectionPeer::GetPacketCreator(
+      &connection_)->IsFecEnabled());
+
+  CongestionBlockWrites();
+
+  // Queue an ack and two stream frames. Ack gets flushed when FEC is turned on
+  // for sending protected data; two stream frames are packing in 1 packet.
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(
+          &connection_, &TestConnection::SendStreamData3WithFec)),
+      IgnoreResult(InvokeWithoutArgs(
+          &connection_, &TestConnection::SendStreamData5WithFec))));
+  connection_.SendAck();
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(3);
+  CongestionUnblockWrites();
+  connection_.GetSendAlarm()->Fire();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's in an fec group.
+  EXPECT_EQ(2u, writer_->header().fec_group);
+  EXPECT_EQ(0u, writer_->frame_count());
+}
+
+TEST_P(QuicConnectionTest, FramePackingAckResponse) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  // Process a data packet to queue up a pending ack.
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+  ProcessDataPacket(1, 1, kEntropyFlag);
+
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData3)),
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData5))));
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+
+  // Process an ack to cause the visitor's OnCanWrite to be invoked.
+  peer_creator_.set_sequence_number(2);
+  QuicAckFrame ack_one = InitAckFrame(0);
+  ProcessAckPacket(&ack_one);
+
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's an ack and two stream frames from
+  // two different streams.
+  EXPECT_EQ(4u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  ASSERT_EQ(2u, writer_->stream_frames().size());
+  EXPECT_EQ(kClientDataStreamId1, writer_->stream_frames()[0].stream_id);
+  EXPECT_EQ(kClientDataStreamId2, writer_->stream_frames()[1].stream_id);
+}
+
+TEST_P(QuicConnectionTest, FramePackingSendv) {
+  // Send data in 1 packet by writing multiple blocks in a single iovector
+  // using writev.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+
+  char data[] = "ABCD";
+  IOVector data_iov;
+  data_iov.AppendNoCoalesce(data, 2);
+  data_iov.AppendNoCoalesce(data + 2, 2);
+  connection_.SendStreamData(1, data_iov, 0, !kFin, MAY_FEC_PROTECT, nullptr);
+
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure multiple iovector blocks have
+  // been packed into a single stream frame from one stream.
+  EXPECT_EQ(1u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  QuicStreamFrame frame = writer_->stream_frames()[0];
+  EXPECT_EQ(1u, frame.stream_id);
+  EXPECT_EQ("ABCD", string(static_cast<char*>
+                           (frame.data.iovec()[0].iov_base),
+                           (frame.data.iovec()[0].iov_len)));
+}
+
+TEST_P(QuicConnectionTest, FramePackingSendvQueued) {
+  // Try to send two stream frames in 1 packet by using writev.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+
+  BlockOnNextWrite();
+  char data[] = "ABCD";
+  IOVector data_iov;
+  data_iov.AppendNoCoalesce(data, 2);
+  data_iov.AppendNoCoalesce(data + 2, 2);
+  connection_.SendStreamData(1, data_iov, 0, !kFin, MAY_FEC_PROTECT, nullptr);
+
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+  EXPECT_TRUE(connection_.HasQueuedData());
+
+  // Unblock the writes and actually send.
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+
+  // Parse the last packet and ensure it's one stream frame from one stream.
+  EXPECT_EQ(1u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_EQ(1u, writer_->stream_frames()[0].stream_id);
+}
+
+TEST_P(QuicConnectionTest, SendingZeroBytes) {
+  // Send a zero byte write with a fin using writev.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  IOVector empty_iov;
+  connection_.SendStreamData(1, empty_iov, 0, kFin, MAY_FEC_PROTECT, nullptr);
+
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_FALSE(connection_.HasQueuedData());
+
+  // Parse the last packet and ensure it's one stream frame from one stream.
+  EXPECT_EQ(1u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_EQ(1u, writer_->stream_frames()[0].stream_id);
+  EXPECT_TRUE(writer_->stream_frames()[0].fin);
+}
+
+TEST_P(QuicConnectionTest, OnCanWrite) {
+  // Visitor's OnCanWrite will send data, but will have more pending writes.
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData3)),
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendStreamData5))));
+  EXPECT_CALL(visitor_, WillingAndAbleToWrite()).WillOnce(Return(true));
+  EXPECT_CALL(*send_algorithm_,
+              TimeUntilSend(_, _, _)).WillRepeatedly(
+                  testing::Return(QuicTime::Delta::Zero()));
+
+  connection_.OnCanWrite();
+
+  // Parse the last packet and ensure it's the two stream frames from
+  // two different streams.
+  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_EQ(2u, writer_->stream_frames().size());
+  EXPECT_EQ(kClientDataStreamId1, writer_->stream_frames()[0].stream_id);
+  EXPECT_EQ(kClientDataStreamId2, writer_->stream_frames()[1].stream_id);
+}
+
+TEST_P(QuicConnectionTest, RetransmitOnNack) {
+  QuicPacketSequenceNumber last_packet;
+  QuicByteCount second_packet_size;
+  SendStreamDataToPeer(3, "foo", 0, !kFin, &last_packet);  // Packet 1
+  second_packet_size =
+      SendStreamDataToPeer(3, "foos", 3, !kFin, &last_packet);  // Packet 2
+  SendStreamDataToPeer(3, "fooos", 7, !kFin, &last_packet);  // Packet 3
+
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Don't lose a packet on an ack, and nothing is retransmitted.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame ack_one = InitAckFrame(1);
+  ProcessAckPacket(&ack_one);
+
+  // Lose a packet and ensure it triggers retransmission.
+  QuicAckFrame nack_two = InitAckFrame(3);
+  NackPacket(2, &nack_two);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(2);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_,
+              OnPacketSent(_, _, _, second_packet_size - kQuicVersionSize, _)).
+                  Times(1);
+  ProcessAckPacket(&nack_two);
+}
+
+TEST_P(QuicConnectionTest, DiscardRetransmit) {
+  QuicPacketSequenceNumber last_packet;
+  SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet);  // Packet 1
+  SendStreamDataToPeer(1, "foos", 3, !kFin, &last_packet);  // Packet 2
+  SendStreamDataToPeer(1, "fooos", 7, !kFin, &last_packet);  // Packet 3
+
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Instigate a loss with an ack.
+  QuicAckFrame nack_two = InitAckFrame(3);
+  NackPacket(2, &nack_two);
+  // The first nack should trigger a fast retransmission, but we'll be
+  // write blocked, so the packet will be queued.
+  BlockOnNextWrite();
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(2);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&nack_two);
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+  // Now, ack the previous transmission.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  QuicAckFrame ack_all = InitAckFrame(3);
+  ProcessAckPacket(&ack_all);
+
+  // Unblock the socket and attempt to send the queued packets.  However,
+  // since the previous transmission has been acked, we will not
+  // send the retransmission.
+  EXPECT_CALL(*send_algorithm_,
+              OnPacketSent(_, _, _, _, _)).Times(0);
+
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, RetransmitNackedLargestObserved) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicPacketSequenceNumber largest_observed;
+  QuicByteCount packet_size;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<2>(&largest_observed), SaveArg<3>(&packet_size),
+                      Return(true)));
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+
+  QuicAckFrame frame = InitAckFrame(1);
+  NackPacket(largest_observed, &frame);
+  // The first nack should retransmit the largest observed packet.
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(1);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_,
+              OnPacketSent(_, _, _, packet_size - kQuicVersionSize, _));
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, QueueAfterTwoRTOs) {
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+    connection_.SendStreamDataWithString(3, "foo", i * 3, !kFin, nullptr);
+  }
+
+  // Block the congestion window and ensure they're queued.
+  BlockOnNextWrite();
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  // Only one packet should be retransmitted.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_TRUE(connection_.HasQueuedData());
+
+  // Unblock the congestion window.
+  writer_->SetWritable();
+  clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(
+      2 * DefaultRetransmissionTime().ToMicroseconds()));
+  // Retransmit already retransmitted packets event though the sequence number
+  // greater than the largest observed.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(10);
+  connection_.GetRetransmissionAlarm()->Fire();
+  connection_.OnCanWrite();
+}
+
+TEST_P(QuicConnectionTest, WriteBlockedThenSent) {
+  BlockOnNextWrite();
+  writer_->set_is_write_blocked_data_buffered(true);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, RetransmitWriteBlockedAckedOriginalThenSent) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+
+  BlockOnNextWrite();
+  writer_->set_is_write_blocked_data_buffered(true);
+  // Simulate the retransmission alarm firing.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(_));
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Ack the sent packet before the callback returns, which happens in
+  // rare circumstances with write blocked sockets.
+  QuicAckFrame ack = InitAckFrame(1);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_, RevertRetransmissionTimeout());
+  ProcessAckPacket(&ack);
+
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  // There is now a pending packet, but with no retransmittable frames.
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  EXPECT_FALSE(connection_.sent_packet_manager().HasRetransmittableFrames(2));
+}
+
+TEST_P(QuicConnectionTest, AlarmsWhenWriteBlocked) {
+  // Block the connection.
+  BlockOnNextWrite();
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+
+  // Set the send and resumption alarms. Fire the alarms and ensure they don't
+  // attempt to write.
+  connection_.GetResumeWritesAlarm()->Set(clock_.ApproximateNow());
+  connection_.GetSendAlarm()->Set(clock_.ApproximateNow());
+  connection_.GetResumeWritesAlarm()->Fire();
+  connection_.GetSendAlarm()->Fire();
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+}
+
+TEST_P(QuicConnectionTest, NoLimitPacketsPerNack) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  int offset = 0;
+  // Send packets 1 to 15.
+  for (int i = 0; i < 15; ++i) {
+    SendStreamDataToPeer(1, "foo", offset, !kFin, nullptr);
+    offset += 3;
+  }
+
+  // Ack 15, nack 1-14.
+  SequenceNumberSet lost_packets;
+  QuicAckFrame nack = InitAckFrame(15);
+  for (int i = 1; i < 15; ++i) {
+    NackPacket(i, &nack);
+    lost_packets.insert(i);
+  }
+
+  // 14 packets have been NACK'd and lost.  In TCP cubic, PRR limits
+  // the retransmission rate in the case of burst losses.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(14);
+  ProcessAckPacket(&nack);
+}
+
+// Test sending multiple acks from the connection to the session.
+TEST_P(QuicConnectionTest, MultipleAcks) {
+  QuicPacketSequenceNumber last_packet;
+  SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet);  // Packet 1
+  EXPECT_EQ(1u, last_packet);
+  SendStreamDataToPeer(3, "foo", 0, !kFin, &last_packet);  // Packet 2
+  EXPECT_EQ(2u, last_packet);
+  SendAckPacketToPeer();  // Packet 3
+  SendStreamDataToPeer(5, "foo", 0, !kFin, &last_packet);  // Packet 4
+  EXPECT_EQ(4u, last_packet);
+  SendStreamDataToPeer(1, "foo", 3, !kFin, &last_packet);  // Packet 5
+  EXPECT_EQ(5u, last_packet);
+  SendStreamDataToPeer(3, "foo", 3, !kFin, &last_packet);  // Packet 6
+  EXPECT_EQ(6u, last_packet);
+
+  // Client will ack packets 1, 2, [!3], 4, 5.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame frame1 = InitAckFrame(5);
+  NackPacket(3, &frame1);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessAckPacket(&frame1);
+
+  // Now the client implicitly acks 3, and explicitly acks 6.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame frame2 = InitAckFrame(6);
+  ProcessAckPacket(&frame2);
+}
+
+TEST_P(QuicConnectionTest, DontLatchUnackedPacket) {
+  SendStreamDataToPeer(1, "foo", 0, !kFin, nullptr);  // Packet 1;
+  // From now on, we send acks, so the send algorithm won't mark them pending.
+  ON_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+              .WillByDefault(Return(false));
+  SendAckPacketToPeer();  // Packet 2
+
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame frame = InitAckFrame(1);
+  ProcessAckPacket(&frame);
+
+  // Verify that our internal state has least-unacked as 2, because we're still
+  // waiting for a potential ack for 2.
+
+  EXPECT_EQ(2u, stop_waiting()->least_unacked);
+
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  frame = InitAckFrame(2);
+  ProcessAckPacket(&frame);
+  EXPECT_EQ(3u, stop_waiting()->least_unacked);
+
+  // When we send an ack, we make sure our least-unacked makes sense.  In this
+  // case since we're not waiting on an ack for 2 and all packets are acked, we
+  // set it to 3.
+  SendAckPacketToPeer();  // Packet 3
+  // Least_unacked remains at 3 until another ack is received.
+  EXPECT_EQ(3u, stop_waiting()->least_unacked);
+  // Check that the outgoing ack had its sequence number as least_unacked.
+  EXPECT_EQ(3u, least_unacked());
+
+  // Ack the ack, which updates the rtt and raises the least unacked.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  frame = InitAckFrame(3);
+  ProcessAckPacket(&frame);
+
+  ON_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+              .WillByDefault(Return(true));
+  SendStreamDataToPeer(1, "bar", 3, false, nullptr);  // Packet 4
+  EXPECT_EQ(4u, stop_waiting()->least_unacked);
+  ON_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+              .WillByDefault(Return(false));
+  SendAckPacketToPeer();  // Packet 5
+  EXPECT_EQ(4u, least_unacked());
+
+  // Send two data packets at the end, and ensure if the last one is acked,
+  // the least unacked is raised above the ack packets.
+  ON_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+              .WillByDefault(Return(true));
+  SendStreamDataToPeer(1, "bar", 6, false, nullptr);  // Packet 6
+  SendStreamDataToPeer(1, "bar", 9, false, nullptr);  // Packet 7
+
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  frame = InitAckFrame(7);
+  NackPacket(5, &frame);
+  NackPacket(6, &frame);
+  ProcessAckPacket(&frame);
+
+  EXPECT_EQ(6u, stop_waiting()->least_unacked);
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketAfterFecPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Don't send missing packet 1.
+  ProcessFecPacket(2, 1, true, !kEntropyFlag, nullptr);
+  // Entropy flag should be false, so entropy should be 0.
+  EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2));
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketWithVaryingSeqNumLengths) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Set up a debug visitor to the connection.
+  FecQuicConnectionDebugVisitor* fec_visitor =
+      new FecQuicConnectionDebugVisitor();
+  connection_.set_debug_visitor(fec_visitor);
+
+  QuicPacketSequenceNumber fec_packet = 0;
+  QuicSequenceNumberLength lengths[] = {PACKET_6BYTE_SEQUENCE_NUMBER,
+                                        PACKET_4BYTE_SEQUENCE_NUMBER,
+                                        PACKET_2BYTE_SEQUENCE_NUMBER,
+                                        PACKET_1BYTE_SEQUENCE_NUMBER};
+  // For each sequence number length size, revive a packet and check sequence
+  // number length in the revived packet.
+  for (size_t i = 0; i < arraysize(lengths); ++i) {
+    // Set sequence_number_length_ (for data and FEC packets).
+    sequence_number_length_ = lengths[i];
+    fec_packet += 2;
+    // Don't send missing packet, but send fec packet right after it.
+    ProcessFecPacket(fec_packet, fec_packet - 1, true, !kEntropyFlag, nullptr);
+    // Sequence number length in the revived header should be the same as
+    // in the original data/fec packet headers.
+    EXPECT_EQ(sequence_number_length_, fec_visitor->revived_header().
+                                       public_header.sequence_number_length);
+  }
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketWithVaryingConnectionIdLengths) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Set up a debug visitor to the connection.
+  FecQuicConnectionDebugVisitor* fec_visitor =
+      new FecQuicConnectionDebugVisitor();
+  connection_.set_debug_visitor(fec_visitor);
+
+  QuicPacketSequenceNumber fec_packet = 0;
+  QuicConnectionIdLength lengths[] = {PACKET_8BYTE_CONNECTION_ID,
+                                      PACKET_4BYTE_CONNECTION_ID,
+                                      PACKET_1BYTE_CONNECTION_ID,
+                                      PACKET_0BYTE_CONNECTION_ID};
+  // For each connection id length size, revive a packet and check connection
+  // id length in the revived packet.
+  for (size_t i = 0; i < arraysize(lengths); ++i) {
+    // Set connection id length (for data and FEC packets).
+    connection_id_length_ = lengths[i];
+    fec_packet += 2;
+    // Don't send missing packet, but send fec packet right after it.
+    ProcessFecPacket(fec_packet, fec_packet - 1, true, !kEntropyFlag, nullptr);
+    // Connection id length in the revived header should be the same as
+    // in the original data/fec packet headers.
+    EXPECT_EQ(connection_id_length_,
+              fec_visitor->revived_header().public_header.connection_id_length);
+  }
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketAfterDataPacketThenFecPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessFecProtectedPacket(1, false, kEntropyFlag);
+  // Don't send missing packet 2.
+  ProcessFecPacket(3, 1, true, !kEntropyFlag, nullptr);
+  // Entropy flag should be true, so entropy should not be 0.
+  EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2));
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketAfterDataPacketsThenFecPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+  // Don't send missing packet 2.
+  ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+  ProcessFecPacket(4, 1, true, kEntropyFlag, nullptr);
+  // Ensure QUIC no longer revives entropy for lost packets.
+  EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2));
+  EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 4));
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketAfterDataPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Don't send missing packet 1.
+  ProcessFecPacket(3, 1, false, !kEntropyFlag, nullptr);
+  // Out of order.
+  ProcessFecProtectedPacket(2, true, !kEntropyFlag);
+  // Entropy flag should be false, so entropy should be 0.
+  EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2));
+}
+
+TEST_P(QuicConnectionTest, ReviveMissingPacketAfterDataPackets) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+  // Don't send missing packet 2.
+  ProcessFecPacket(6, 1, false, kEntropyFlag, nullptr);
+  ProcessFecProtectedPacket(3, false, kEntropyFlag);
+  ProcessFecProtectedPacket(4, false, kEntropyFlag);
+  ProcessFecProtectedPacket(5, true, !kEntropyFlag);
+  // Ensure entropy is not revived for the missing packet.
+  EXPECT_EQ(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 2));
+  EXPECT_NE(0u, QuicConnectionPeer::ReceivedEntropyHash(&connection_, 3));
+}
+
+TEST_P(QuicConnectionTest, TLP) {
+  QuicSentPacketManagerPeer::SetMaxTailLossProbes(
+      QuicConnectionPeer::GetSentPacketManager(&connection_), 1);
+
+  SendStreamDataToPeer(3, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+  QuicTime retransmission_time =
+      connection_.GetRetransmissionAlarm()->deadline();
+  EXPECT_NE(QuicTime::Zero(), retransmission_time);
+
+  EXPECT_EQ(1u, writer_->header().packet_sequence_number);
+  // Simulate the retransmission alarm firing and sending a tlp,
+  // so send algorithm's OnRetransmissionTimeout is not called.
+  clock_.AdvanceTime(retransmission_time.Subtract(clock_.Now()));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 2u, _, _));
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(2u, writer_->header().packet_sequence_number);
+  // We do not raise the high water mark yet.
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+}
+
+TEST_P(QuicConnectionTest, RTO) {
+  QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+      DefaultRetransmissionTime());
+  SendStreamDataToPeer(3, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+
+  EXPECT_EQ(1u, writer_->header().packet_sequence_number);
+  EXPECT_EQ(default_retransmission_time,
+            connection_.GetRetransmissionAlarm()->deadline());
+  // Simulate the retransmission alarm firing.
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 2u, _, _));
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(2u, writer_->header().packet_sequence_number);
+  // We do not raise the high water mark yet.
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+}
+
+TEST_P(QuicConnectionTest, RTOWithSameEncryptionLevel) {
+  QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+      DefaultRetransmissionTime());
+  use_tagging_decrypter();
+
+  // A TaggingEncrypter puts kTagSize copies of the given byte (0x01 here) at
+  // the end of the packet. We can test this to check which encrypter was used.
+  connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+  SendStreamDataToPeer(3, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet());
+
+  connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(0x02));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+  SendStreamDataToPeer(3, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet());
+
+  EXPECT_EQ(default_retransmission_time,
+            connection_.GetRetransmissionAlarm()->deadline());
+  {
+    InSequence s;
+    EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 3, _, _));
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 4, _, _));
+  }
+
+  // Simulate the retransmission alarm firing.
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Packet should have been sent with ENCRYPTION_NONE.
+  EXPECT_EQ(0x01010101u, writer_->final_bytes_of_previous_packet());
+
+  // Packet should have been sent with ENCRYPTION_INITIAL.
+  EXPECT_EQ(0x02020202u, writer_->final_bytes_of_last_packet());
+}
+
+TEST_P(QuicConnectionTest, SendHandshakeMessages) {
+  use_tagging_decrypter();
+  // A TaggingEncrypter puts kTagSize copies of the given byte (0x01 here) at
+  // the end of the packet. We can test this to check which encrypter was used.
+  connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+
+  // Attempt to send a handshake message and have the socket block.
+  EXPECT_CALL(*send_algorithm_,
+              TimeUntilSend(_, _, _)).WillRepeatedly(
+                  testing::Return(QuicTime::Delta::Zero()));
+  BlockOnNextWrite();
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  // The packet should be serialized, but not queued.
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+  // Switch to the new encrypter.
+  connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(0x02));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+
+  // Now become writeable and flush the packets.
+  writer_->SetWritable();
+  EXPECT_CALL(visitor_, OnCanWrite());
+  connection_.OnCanWrite();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+
+  // Verify that the handshake packet went out at the null encryption.
+  EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet());
+}
+
+TEST_P(QuicConnectionTest,
+       DropRetransmitsForNullEncryptedPacketAfterForwardSecure) {
+  use_tagging_decrypter();
+  connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+  QuicPacketSequenceNumber sequence_number;
+  SendStreamDataToPeer(3, "foo", 0, !kFin, &sequence_number);
+
+  // Simulate the retransmission alarm firing and the socket blocking.
+  BlockOnNextWrite();
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Go forward secure.
+  connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                           new TaggingEncrypter(0x02));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_.NeuterUnencryptedPackets();
+
+  EXPECT_EQ(QuicTime::Zero(),
+            connection_.GetRetransmissionAlarm()->deadline());
+  // Unblock the socket and ensure that no packets are sent.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+}
+
+TEST_P(QuicConnectionTest, RetransmitPacketsWithInitialEncryption) {
+  use_tagging_decrypter();
+  connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_NONE);
+
+  SendStreamDataToPeer(1, "foo", 0, !kFin, nullptr);
+
+  connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(0x02));
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+
+  SendStreamDataToPeer(2, "bar", 0, !kFin, nullptr);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+
+  connection_.RetransmitUnackedPackets(ALL_INITIAL_RETRANSMISSION);
+}
+
+TEST_P(QuicConnectionTest, BufferNonDecryptablePackets) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  use_tagging_decrypter();
+
+  const uint8 tag = 0x07;
+  framer_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag));
+
+  // Process an encrypted packet which can not yet be decrypted
+  // which should result in the packet being buffered.
+  ProcessDataPacketAtLevel(1, 0, kEntropyFlag, ENCRYPTION_INITIAL);
+
+  // Transition to the new encryption state and process another
+  // encrypted packet which should result in the original packet being
+  // processed.
+  connection_.SetDecrypter(new StrictTaggingDecrypter(tag),
+                           ENCRYPTION_INITIAL);
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+  connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag));
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(2);
+  ProcessDataPacketAtLevel(2, 0, kEntropyFlag, ENCRYPTION_INITIAL);
+
+  // Finally, process a third packet and note that we do not
+  // reprocess the buffered packet.
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+  ProcessDataPacketAtLevel(3, 0, kEntropyFlag, ENCRYPTION_INITIAL);
+}
+
+TEST_P(QuicConnectionTest, TestRetransmitOrder) {
+  QuicByteCount first_packet_size;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).WillOnce(
+      DoAll(SaveArg<3>(&first_packet_size), Return(true)));
+
+  connection_.SendStreamDataWithString(3, "first_packet", 0, !kFin, nullptr);
+  QuicByteCount second_packet_size;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).WillOnce(
+      DoAll(SaveArg<3>(&second_packet_size), Return(true)));
+  connection_.SendStreamDataWithString(3, "second_packet", 12, !kFin, nullptr);
+  EXPECT_NE(first_packet_size, second_packet_size);
+  // Advance the clock by huge time to make sure packets will be retransmitted.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  {
+    InSequence s;
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, first_packet_size, _));
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, second_packet_size, _));
+  }
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Advance again and expect the packets to be sent again in the same order.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(20));
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  {
+    InSequence s;
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, first_packet_size, _));
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, second_packet_size, _));
+  }
+  connection_.GetRetransmissionAlarm()->Fire();
+}
+
+TEST_P(QuicConnectionTest, RetransmissionCountCalculation) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicPacketSequenceNumber original_sequence_number;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<2>(&original_sequence_number), Return(true)));
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+
+  EXPECT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+      &connection_, original_sequence_number));
+  EXPECT_FALSE(QuicConnectionPeer::IsRetransmission(
+      &connection_, original_sequence_number));
+  // Force retransmission due to RTO.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  QuicPacketSequenceNumber rto_sequence_number;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<2>(&rto_sequence_number), Return(true)));
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_FALSE(QuicConnectionPeer::IsSavedForRetransmission(
+      &connection_, original_sequence_number));
+  ASSERT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+      &connection_, rto_sequence_number));
+  EXPECT_TRUE(QuicConnectionPeer::IsRetransmission(
+      &connection_, rto_sequence_number));
+  // Once by explicit nack.
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(rto_sequence_number);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicPacketSequenceNumber nack_sequence_number = 0;
+  // Ack packets might generate some other packets, which are not
+  // retransmissions. (More ack packets).
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(AnyNumber());
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(DoAll(SaveArg<2>(&nack_sequence_number), Return(true)));
+  QuicAckFrame ack = InitAckFrame(rto_sequence_number);
+  // Nack the retransmitted packet.
+  NackPacket(original_sequence_number, &ack);
+  NackPacket(rto_sequence_number, &ack);
+  ProcessAckPacket(&ack);
+
+  ASSERT_NE(0u, nack_sequence_number);
+  EXPECT_FALSE(QuicConnectionPeer::IsSavedForRetransmission(
+      &connection_, rto_sequence_number));
+  ASSERT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+      &connection_, nack_sequence_number));
+  EXPECT_TRUE(QuicConnectionPeer::IsRetransmission(
+      &connection_, nack_sequence_number));
+}
+
+TEST_P(QuicConnectionTest, SetRTOAfterWritingToSocket) {
+  BlockOnNextWrite();
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  // Make sure that RTO is not started when the packet is queued.
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+
+  // Test that RTO is started once we write to the socket.
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, DelayRTOWithAckReceipt) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(2);
+  connection_.SendStreamDataWithString(2, "foo", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(3, "bar", 0, !kFin, nullptr);
+  QuicAlarm* retransmission_alarm = connection_.GetRetransmissionAlarm();
+  EXPECT_TRUE(retransmission_alarm->IsSet());
+  EXPECT_EQ(clock_.Now().Add(DefaultRetransmissionTime()),
+            retransmission_alarm->deadline());
+
+  // Advance the time right before the RTO, then receive an ack for the first
+  // packet to delay the RTO.
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame ack = InitAckFrame(1);
+  ProcessAckPacket(&ack);
+  EXPECT_TRUE(retransmission_alarm->IsSet());
+  EXPECT_GT(retransmission_alarm->deadline(), clock_.Now());
+
+  // Move forward past the original RTO and ensure the RTO is still pending.
+  clock_.AdvanceTime(DefaultRetransmissionTime().Multiply(2));
+
+  // Ensure the second packet gets retransmitted when it finally fires.
+  EXPECT_TRUE(retransmission_alarm->IsSet());
+  EXPECT_LT(retransmission_alarm->deadline(), clock_.ApproximateNow());
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  // Manually cancel the alarm to simulate a real test.
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // The new retransmitted sequence number should set the RTO to a larger value
+  // than previously.
+  EXPECT_TRUE(retransmission_alarm->IsSet());
+  QuicTime next_rto_time = retransmission_alarm->deadline();
+  QuicTime expected_rto_time =
+      connection_.sent_packet_manager().GetRetransmissionTime();
+  EXPECT_EQ(next_rto_time, expected_rto_time);
+}
+
+TEST_P(QuicConnectionTest, TestQueued) {
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  BlockOnNextWrite();
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+  // Unblock the writes and actually send.
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, CloseFecGroup) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  // Don't send missing packet 1.
+  // Don't send missing packet 2.
+  ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+  // Don't send missing FEC packet 3.
+  ASSERT_EQ(1u, connection_.NumFecGroups());
+
+  // Now send non-fec protected ack packet and close the group.
+  peer_creator_.set_sequence_number(4);
+  QuicStopWaitingFrame frame = InitStopWaitingFrame(5);
+  ProcessStopWaitingPacket(&frame);
+  ASSERT_EQ(0u, connection_.NumFecGroups());
+}
+
+TEST_P(QuicConnectionTest, NoQuicCongestionFeedbackFrame) {
+  SendAckPacketToPeer();
+  EXPECT_TRUE(writer_->feedback_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, WithQuicCongestionFeedbackFrame) {
+  QuicCongestionFeedbackFrame info;
+  info.type = kTCP;
+  info.tcp.receive_window = 0x4030;
+
+  // After QUIC_VERSION_22, do not send TCP Congestion Feedback Frames anymore.
+  if (version() > QUIC_VERSION_22) {
+    SendAckPacketToPeer();
+    ASSERT_TRUE(writer_->feedback_frames().empty());
+  } else {
+    // Only SetFeedback in this case because SetFeedback will create a receive
+    // algorithm which is how the received_packet_manager checks if it should be
+    // creating TCP Congestion Feedback Frames.
+    SetFeedback(&info);
+    SendAckPacketToPeer();
+    ASSERT_FALSE(writer_->feedback_frames().empty());
+    ASSERT_EQ(kTCP, writer_->feedback_frames()[0].type);
+  }
+}
+
+TEST_P(QuicConnectionTest, UpdateQuicCongestionFeedbackFrame) {
+  SendAckPacketToPeer();
+  EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+}
+
+TEST_P(QuicConnectionTest, DontUpdateQuicCongestionFeedbackFrameForRevived) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  SendAckPacketToPeer();
+  // Process an FEC packet, and revive the missing data packet
+  // but only contact the receive_algorithm once.
+  EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _));
+  ProcessFecPacket(2, 1, true, !kEntropyFlag, nullptr);
+}
+
+TEST_P(QuicConnectionTest, InitialTimeout) {
+  if (!FLAGS_quic_unified_timeouts) {
+    EXPECT_TRUE(connection_.connected());
+    EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_CONNECTION_TIMED_OUT, false));
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
+
+    QuicTime default_timeout = clock_.ApproximateNow().Add(
+        QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+    // Simulate the timeout alarm firing.
+    clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
+    connection_.GetTimeoutAlarm()->Fire();
+
+    EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+    EXPECT_FALSE(connection_.connected());
+
+    EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+    EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+    EXPECT_FALSE(connection_.GetResumeWritesAlarm()->IsSet());
+    EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+    EXPECT_FALSE(connection_.GetSendAlarm()->IsSet());
+    return;
+  }
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
+  EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+
+  // SetFromConfig sets the initial timeouts before negotiation.
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+  config.SetDefaults();
+  connection_.SetFromConfig(config);
+  // Subtract a second from the idle timeout on the client side.
+  QuicTime default_timeout = clock_.ApproximateNow().Add(
+      QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs - 1));
+  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_CONNECTION_TIMED_OUT, false));
+  // Simulate the timeout alarm firing.
+  clock_.AdvanceTime(
+      QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs - 1));
+  connection_.GetTimeoutAlarm()->Fire();
+
+  EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_FALSE(connection_.connected());
+
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetResumeWritesAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetSendAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, OverallTimeout) {
+  // Use a shorter overall connection timeout than idle timeout for this test.
+  const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5);
+  connection_.SetNetworkTimeouts(timeout, timeout);
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
+
+  QuicTime overall_timeout = clock_.ApproximateNow().Add(timeout).Subtract(
+      QuicTime::Delta::FromSeconds(1));
+  EXPECT_EQ(overall_timeout, connection_.GetTimeoutAlarm()->deadline());
+  EXPECT_TRUE(connection_.connected());
+
+  // Send and ack new data 3 seconds later to lengthen the idle timeout.
+  SendStreamDataToPeer(1, "GET /", 0, kFin, nullptr);
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(3));
+  QuicAckFrame frame = InitAckFrame(1);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&frame);
+
+  // Fire early to verify it wouldn't timeout yet.
+  connection_.GetTimeoutAlarm()->Fire();
+  EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_TRUE(connection_.connected());
+
+  clock_.AdvanceTime(timeout.Subtract(QuicTime::Delta::FromSeconds(2)));
+
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(QUIC_CONNECTION_OVERALL_TIMED_OUT, false));
+  // Simulate the timeout alarm firing.
+  connection_.GetTimeoutAlarm()->Fire();
+
+  EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_FALSE(connection_.connected());
+
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetResumeWritesAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetSendAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, PingAfterSend) {
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(visitor_, HasOpenDataStreams()).WillRepeatedly(Return(true));
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+
+  // Advance to 5ms, and send a packet to the peer, which will set
+  // the ping alarm.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  SendStreamDataToPeer(1, "GET /", 0, kFin, nullptr);
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_EQ(clock_.ApproximateNow().Add(QuicTime::Delta::FromSeconds(15)),
+            connection_.GetPingAlarm()->deadline());
+
+  // Now recevie and ACK of the previous packet, which will move the
+  // ping alarm forward.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  QuicAckFrame frame = InitAckFrame(1);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&frame);
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  // The ping timer is set slightly less than 15 seconds in the future, because
+  // of the 1s ping timer alarm granularity.
+  EXPECT_EQ(clock_.ApproximateNow().Add(QuicTime::Delta::FromSeconds(15))
+                .Subtract(QuicTime::Delta::FromMilliseconds(5)),
+            connection_.GetPingAlarm()->deadline());
+
+  writer_->Reset();
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+  connection_.GetPingAlarm()->Fire();
+  EXPECT_EQ(1u, writer_->frame_count());
+  if (version() >= QUIC_VERSION_18) {
+    ASSERT_EQ(1u, writer_->ping_frames().size());
+  } else {
+    ASSERT_EQ(1u, writer_->stream_frames().size());
+    EXPECT_EQ(kCryptoStreamId, writer_->stream_frames()[0].stream_id);
+    EXPECT_EQ(0u, writer_->stream_frames()[0].offset);
+  }
+  writer_->Reset();
+
+  EXPECT_CALL(visitor_, HasOpenDataStreams()).WillRepeatedly(Return(false));
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  SendAckPacketToPeer();
+
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, TimeoutAfterSend) {
+  if (!FLAGS_quic_unified_timeouts) {
+    EXPECT_TRUE(connection_.connected());
+
+    QuicTime default_timeout = clock_.ApproximateNow().Add(
+        QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
+
+    // When we send a packet, the timeout will change to 5000 +
+    // kDefaultInitialTimeoutSecs.
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+
+    // Send an ack so we don't set the retransmission alarm.
+    SendAckPacketToPeer();
+    EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+    // The original alarm will fire.  We should not time out because we had a
+    // network event at t=5000.  The alarm will reregister.
+    clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(
+        kDefaultIdleTimeoutSecs * 1000000 - 5000));
+    EXPECT_EQ(default_timeout, clock_.ApproximateNow());
+    connection_.GetTimeoutAlarm()->Fire();
+    EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+    EXPECT_TRUE(connection_.connected());
+    EXPECT_EQ(default_timeout.Add(QuicTime::Delta::FromMilliseconds(5)),
+              connection_.GetTimeoutAlarm()->deadline());
+
+    // This time, we should time out.
+    EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_CONNECTION_TIMED_OUT, false));
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+    EXPECT_EQ(default_timeout.Add(QuicTime::Delta::FromMilliseconds(5)),
+              clock_.ApproximateNow());
+    connection_.GetTimeoutAlarm()->Fire();
+    EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+    EXPECT_FALSE(connection_.connected());
+    return;
+  }
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+  config.SetDefaults();
+  connection_.SetFromConfig(config);
+
+  const QuicTime::Delta initial_idle_timeout =
+      QuicTime::Delta::FromSeconds(kInitialIdleTimeoutSecs - 1);
+  const QuicTime::Delta five_ms = QuicTime::Delta::FromMilliseconds(5);
+  QuicTime default_timeout = clock_.ApproximateNow().Add(initial_idle_timeout);
+
+  // When we send a packet, the timeout will change to 5ms +
+  // kInitialIdleTimeoutSecs.
+  clock_.AdvanceTime(five_ms);
+
+  // Send an ack so we don't set the retransmission alarm.
+  SendAckPacketToPeer();
+  EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+  // The original alarm will fire.  We should not time out because we had a
+  // network event at t=5ms.  The alarm will reregister.
+  clock_.AdvanceTime(initial_idle_timeout.Subtract(five_ms));
+  EXPECT_EQ(default_timeout, clock_.ApproximateNow());
+  connection_.GetTimeoutAlarm()->Fire();
+  EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_EQ(default_timeout.Add(five_ms),
+            connection_.GetTimeoutAlarm()->deadline());
+
+  // This time, we should time out.
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_CONNECTION_TIMED_OUT, false));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  clock_.AdvanceTime(five_ms);
+  EXPECT_EQ(default_timeout.Add(five_ms), clock_.ApproximateNow());
+  connection_.GetTimeoutAlarm()->Fire();
+  EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_FALSE(connection_.connected());
+}
+
+TEST_P(QuicConnectionTest, SendScheduler) {
+  // Test that if we send a packet without delay, it is not queued.
+  QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  connection_.SendPacket(
+      ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, SendSchedulerEAGAIN) {
+  QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+  BlockOnNextWrite();
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 1, _, _)).Times(0);
+  connection_.SendPacket(
+      ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, TestQueueLimitsOnSendStreamData) {
+  // All packets carry version info till version is negotiated.
+  size_t payload_length;
+  size_t length = GetPacketLengthForOneStream(
+      connection_.version(), kIncludeVersion, PACKET_1BYTE_SEQUENCE_NUMBER,
+      NOT_IN_FEC_GROUP, &payload_length);
+  QuicConnectionPeer::GetPacketCreator(&connection_)->set_max_packet_length(
+      length);
+
+  // Queue the first packet.
+  EXPECT_CALL(*send_algorithm_,
+              TimeUntilSend(_, _, _)).WillOnce(
+                  testing::Return(QuicTime::Delta::FromMicroseconds(10)));
+  const string payload(payload_length, 'a');
+  EXPECT_EQ(0u, connection_.SendStreamDataWithString(3, payload, 0, !kFin,
+                                                     nullptr).bytes_consumed);
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_P(QuicConnectionTest, LoopThroughSendingPackets) {
+  // All packets carry version info till version is negotiated.
+  size_t payload_length;
+  // GetPacketLengthForOneStream() assumes a stream offset of 0 in determining
+  // packet length. The size of the offset field in a stream frame is 0 for
+  // offset 0, and 2 for non-zero offsets up through 16K. Increase
+  // max_packet_length by 2 so that subsequent packets containing subsequent
+  // stream frames with non-zero offets will fit within the packet length.
+  size_t length = 2 + GetPacketLengthForOneStream(
+          connection_.version(), kIncludeVersion, PACKET_1BYTE_SEQUENCE_NUMBER,
+          NOT_IN_FEC_GROUP, &payload_length);
+  QuicConnectionPeer::GetPacketCreator(&connection_)->set_max_packet_length(
+      length);
+
+  // Queue the first packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(7);
+  // The first stream frame will have 2 fewer overhead bytes than the other six.
+  const string payload(payload_length * 7 + 2, 'a');
+  EXPECT_EQ(payload.size(),
+            connection_.SendStreamDataWithString(1, payload, 0, !kFin, nullptr)
+                .bytes_consumed);
+}
+
+TEST_P(QuicConnectionTest, SendDelayedAck) {
+  QuicTime ack_time = clock_.ApproximateNow().Add(DefaultDelayedAckTime());
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  const uint8 tag = 0x07;
+  connection_.SetDecrypter(new StrictTaggingDecrypter(tag),
+                           ENCRYPTION_INITIAL);
+  framer_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag));
+  // Process a packet from the non-crypto stream.
+  frame1_.stream_id = 3;
+
+  // The same as ProcessPacket(1) except that ENCRYPTION_INITIAL is used
+  // instead of ENCRYPTION_NONE.
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+  ProcessDataPacketAtLevel(1, 0, !kEntropyFlag, ENCRYPTION_INITIAL);
+
+  // Check if delayed ack timer is running for the expected interval.
+  EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
+  EXPECT_EQ(ack_time, connection_.GetAckAlarm()->deadline());
+  // Simulate delayed ack alarm firing.
+  connection_.GetAckAlarm()->Fire();
+  // Check that ack is sent and that delayed ack alarm is reset.
+  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, SendEarlyDelayedAckForCrypto) {
+  QuicTime ack_time = clock_.ApproximateNow();
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  // Process a packet from the crypto stream, which is frame1_'s default.
+  ProcessPacket(1);
+  // Check if delayed ack timer is running for the expected interval.
+  EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
+  EXPECT_EQ(ack_time, connection_.GetAckAlarm()->deadline());
+  // Simulate delayed ack alarm firing.
+  connection_.GetAckAlarm()->Fire();
+  // Check that ack is sent and that delayed ack alarm is reset.
+  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, SendDelayedAckOnSecondPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+  ProcessPacket(2);
+  // Check that ack is sent and that delayed ack alarm is reset.
+  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, SendDelayedAckForPing) {
+  if (version() < QUIC_VERSION_18) {
+    return;
+  }
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  ProcessPingPacket(1);
+  EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, NoAckOnOldNacks) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  // Drop one packet, triggering a sequence of acks.
+  ProcessPacket(2);
+  size_t frames_per_ack = 2;
+  EXPECT_EQ(frames_per_ack, writer_->frame_count());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  writer_->Reset();
+  ProcessPacket(3);
+  EXPECT_EQ(frames_per_ack, writer_->frame_count());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  writer_->Reset();
+  ProcessPacket(4);
+  EXPECT_EQ(frames_per_ack, writer_->frame_count());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  writer_->Reset();
+  ProcessPacket(5);
+  EXPECT_EQ(frames_per_ack, writer_->frame_count());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  writer_->Reset();
+  // Now only set the timer on the 6th packet, instead of sending another ack.
+  ProcessPacket(6);
+  EXPECT_EQ(0u, writer_->frame_count());
+  EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, SendDelayedAckOnOutgoingPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+  connection_.SendStreamDataWithString(kClientDataStreamId1, "foo", 0, !kFin,
+                                       nullptr);
+  // Check that ack is bundled with outgoing data and that delayed ack
+  // alarm is reset.
+  EXPECT_EQ(3u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, SendDelayedAckOnOutgoingCryptoPacket) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+  connection_.SendStreamDataWithString(kCryptoStreamId, "foo", 0, !kFin,
+                                       nullptr);
+  // Check that ack is bundled with outgoing crypto data.
+  EXPECT_EQ(3u, writer_->frame_count());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, BlockAndBufferOnFirstCHLOPacketOfTwo) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+  BlockOnNextWrite();
+  writer_->set_is_write_blocked_data_buffered(true);
+  connection_.SendStreamDataWithString(kCryptoStreamId, "foo", 0, !kFin,
+                                       nullptr);
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+  EXPECT_FALSE(connection_.HasQueuedData());
+  connection_.SendStreamDataWithString(kCryptoStreamId, "bar", 3, !kFin,
+                                       nullptr);
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+  EXPECT_TRUE(connection_.HasQueuedData());
+}
+
+TEST_P(QuicConnectionTest, BundleAckForSecondCHLO) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(
+      IgnoreResult(InvokeWithoutArgs(&connection_,
+                                     &TestConnection::SendCryptoStreamData)));
+  // Process a packet from the crypto stream, which is frame1_'s default.
+  // Receiving the CHLO as packet 2 first will cause the connection to
+  // immediately send an ack, due to the packet gap.
+  ProcessPacket(2);
+  // Check that ack is sent and that delayed ack alarm is reset.
+  EXPECT_EQ(3u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, BundleAckWithDataOnIncomingAck) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  connection_.SendStreamDataWithString(kClientDataStreamId1, "foo", 0, !kFin,
+                                       nullptr);
+  connection_.SendStreamDataWithString(kClientDataStreamId1, "foo", 3, !kFin,
+                                       nullptr);
+  // Ack the second packet, which will retransmit the first packet.
+  QuicAckFrame ack = InitAckFrame(2);
+  NackPacket(1, &ack);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(1);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&ack);
+  EXPECT_EQ(1u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  writer_->Reset();
+
+  // Now ack the retransmission, which will both raise the high water mark
+  // and see if there is more data to send.
+  ack = InitAckFrame(3);
+  NackPacket(1, &ack);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&ack);
+
+  // Check that no packet is sent and the ack alarm isn't set.
+  EXPECT_EQ(0u, writer_->frame_count());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+  writer_->Reset();
+
+  // Send the same ack, but send both data and an ack together.
+  ack = InitAckFrame(3);
+  NackPacket(1, &ack);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(
+      IgnoreResult(InvokeWithoutArgs(
+          &connection_,
+          &TestConnection::EnsureWritableAndSendStreamData5)));
+  ProcessAckPacket(&ack);
+
+  // Check that ack is bundled with outgoing data and the delayed ack
+  // alarm is reset.
+  EXPECT_EQ(3u, writer_->frame_count());
+  EXPECT_FALSE(writer_->stop_waiting_frames().empty());
+  EXPECT_FALSE(writer_->ack_frames().empty());
+  EXPECT_EQ(1u, writer_->stream_frames().size());
+  EXPECT_FALSE(connection_.GetAckAlarm()->IsSet());
+}
+
+TEST_P(QuicConnectionTest, NoAckSentForClose) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessPacket(1);
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_PEER_GOING_AWAY, true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+  ProcessClosePacket(2, 0);
+}
+
+TEST_P(QuicConnectionTest, SendWhenDisconnected) {
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_PEER_GOING_AWAY, false));
+  connection_.CloseConnection(QUIC_PEER_GOING_AWAY, false);
+  EXPECT_FALSE(connection_.connected());
+  EXPECT_FALSE(connection_.CanWriteStreamData());
+  QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 1, _, _)).Times(0);
+  connection_.SendPacket(
+      ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+}
+
+TEST_P(QuicConnectionTest, PublicReset) {
+  QuicPublicResetPacket header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = true;
+  header.public_header.version_flag = false;
+  header.rejected_sequence_number = 10101;
+  scoped_ptr<QuicEncryptedPacket> packet(
+      framer_.BuildPublicResetPacket(header));
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_PUBLIC_RESET, true));
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *packet);
+}
+
+TEST_P(QuicConnectionTest, GoAway) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  QuicGoAwayFrame goaway;
+  goaway.last_good_stream_id = 1;
+  goaway.error_code = QUIC_PEER_GOING_AWAY;
+  goaway.reason_phrase = "Going away.";
+  EXPECT_CALL(visitor_, OnGoAway(_));
+  ProcessGoAwayPacket(&goaway);
+}
+
+TEST_P(QuicConnectionTest, WindowUpdate) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  QuicWindowUpdateFrame window_update;
+  window_update.stream_id = 3;
+  window_update.byte_offset = 1234;
+  EXPECT_CALL(visitor_, OnWindowUpdateFrames(_));
+  ProcessFramePacket(QuicFrame(&window_update));
+}
+
+TEST_P(QuicConnectionTest, Blocked) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  QuicBlockedFrame blocked;
+  blocked.stream_id = 3;
+  EXPECT_CALL(visitor_, OnBlockedFrames(_));
+  ProcessFramePacket(QuicFrame(&blocked));
+}
+
+TEST_P(QuicConnectionTest, InvalidPacket) {
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(QUIC_INVALID_PACKET_HEADER, false));
+  QuicEncryptedPacket encrypted(nullptr, 0);
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), encrypted);
+  // The connection close packet should have error details.
+  ASSERT_FALSE(writer_->connection_close_frames().empty());
+  EXPECT_EQ("Unable to read public flags.",
+            writer_->connection_close_frames()[0].error_details);
+}
+
+TEST_P(QuicConnectionTest, MissingPacketsBeforeLeastUnacked) {
+  // Set the sequence number of the ack packet to be least unacked (4).
+  peer_creator_.set_sequence_number(3);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicStopWaitingFrame frame = InitStopWaitingFrame(4);
+  ProcessStopWaitingPacket(&frame);
+  EXPECT_TRUE(outgoing_ack()->missing_packets.empty());
+}
+
+TEST_P(QuicConnectionTest, ReceivedEntropyHashCalculation) {
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessDataPacket(1, 1, kEntropyFlag);
+  ProcessDataPacket(4, 1, kEntropyFlag);
+  ProcessDataPacket(3, 1, !kEntropyFlag);
+  ProcessDataPacket(7, 1, kEntropyFlag);
+  EXPECT_EQ(146u, outgoing_ack()->entropy_hash);
+}
+
+TEST_P(QuicConnectionTest, ReceivedEntropyHashCalculationHalfFEC) {
+  // FEC packets should not change the entropy hash calculation.
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessDataPacket(1, 1, kEntropyFlag);
+  ProcessFecPacket(4, 1, false, kEntropyFlag, nullptr);
+  ProcessDataPacket(3, 3, !kEntropyFlag);
+  ProcessFecPacket(7, 3, false, kEntropyFlag, nullptr);
+  EXPECT_EQ(146u, outgoing_ack()->entropy_hash);
+}
+
+TEST_P(QuicConnectionTest, UpdateEntropyForReceivedPackets) {
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessDataPacket(1, 1, kEntropyFlag);
+  ProcessDataPacket(5, 1, kEntropyFlag);
+  ProcessDataPacket(4, 1, !kEntropyFlag);
+  EXPECT_EQ(34u, outgoing_ack()->entropy_hash);
+  // Make 4th packet my least unacked, and update entropy for 2, 3 packets.
+  peer_creator_.set_sequence_number(5);
+  QuicPacketEntropyHash six_packet_entropy_hash = 0;
+  QuicPacketEntropyHash kRandomEntropyHash = 129u;
+  QuicStopWaitingFrame frame = InitStopWaitingFrame(4);
+  frame.entropy_hash = kRandomEntropyHash;
+  if (ProcessStopWaitingPacket(&frame)) {
+    six_packet_entropy_hash = 1 << 6;
+  }
+
+  EXPECT_EQ((kRandomEntropyHash + (1 << 5) + six_packet_entropy_hash),
+            outgoing_ack()->entropy_hash);
+}
+
+TEST_P(QuicConnectionTest, UpdateEntropyHashUptoCurrentPacket) {
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  ProcessDataPacket(1, 1, kEntropyFlag);
+  ProcessDataPacket(5, 1, !kEntropyFlag);
+  ProcessDataPacket(22, 1, kEntropyFlag);
+  EXPECT_EQ(66u, outgoing_ack()->entropy_hash);
+  peer_creator_.set_sequence_number(22);
+  QuicPacketEntropyHash kRandomEntropyHash = 85u;
+  // Current packet is the least unacked packet.
+  QuicPacketEntropyHash ack_entropy_hash;
+  QuicStopWaitingFrame frame = InitStopWaitingFrame(23);
+  frame.entropy_hash = kRandomEntropyHash;
+  ack_entropy_hash = ProcessStopWaitingPacket(&frame);
+  EXPECT_EQ((kRandomEntropyHash + ack_entropy_hash),
+            outgoing_ack()->entropy_hash);
+  ProcessDataPacket(25, 1, kEntropyFlag);
+  EXPECT_EQ((kRandomEntropyHash + ack_entropy_hash + (1 << (25 % 8))),
+            outgoing_ack()->entropy_hash);
+}
+
+TEST_P(QuicConnectionTest, EntropyCalculationForTruncatedAck) {
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  QuicPacketEntropyHash entropy[51];
+  entropy[0] = 0;
+  for (int i = 1; i < 51; ++i) {
+    bool should_send = i % 10 != 1;
+    bool entropy_flag = (i & (i - 1)) != 0;
+    if (!should_send) {
+      entropy[i] = entropy[i - 1];
+      continue;
+    }
+    if (entropy_flag) {
+      entropy[i] = entropy[i - 1] ^ (1 << (i % 8));
+    } else {
+      entropy[i] = entropy[i - 1];
+    }
+    ProcessDataPacket(i, 1, entropy_flag);
+  }
+  for (int i = 1; i < 50; ++i) {
+    EXPECT_EQ(entropy[i], QuicConnectionPeer::ReceivedEntropyHash(
+        &connection_, i));
+  }
+}
+
+TEST_P(QuicConnectionTest, ServerSendsVersionNegotiationPacket) {
+  connection_.SetSupportedVersions(QuicSupportedVersions());
+  framer_.set_version_for_tests(QUIC_VERSION_UNSUPPORTED);
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.packet_sequence_number = 12;
+  header.fec_group = 0;
+
+  QuicFrames frames;
+  QuicFrame frame(&frame1_);
+  frames.push_back(frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer_, header, frames).packet);
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+
+  framer_.set_version(version());
+  connection_.set_is_server(true);
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+  EXPECT_TRUE(writer_->version_negotiation_packet() != nullptr);
+
+  size_t num_versions = arraysize(kSupportedQuicVersions);
+  ASSERT_EQ(num_versions,
+            writer_->version_negotiation_packet()->versions.size());
+
+  // We expect all versions in kSupportedQuicVersions to be
+  // included in the packet.
+  for (size_t i = 0; i < num_versions; ++i) {
+    EXPECT_EQ(kSupportedQuicVersions[i],
+              writer_->version_negotiation_packet()->versions[i]);
+  }
+}
+
+TEST_P(QuicConnectionTest, ServerSendsVersionNegotiationPacketSocketBlocked) {
+  connection_.SetSupportedVersions(QuicSupportedVersions());
+  framer_.set_version_for_tests(QUIC_VERSION_UNSUPPORTED);
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.packet_sequence_number = 12;
+  header.fec_group = 0;
+
+  QuicFrames frames;
+  QuicFrame frame(&frame1_);
+  frames.push_back(frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer_, header, frames).packet);
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+
+  framer_.set_version(version());
+  connection_.set_is_server(true);
+  BlockOnNextWrite();
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+  EXPECT_EQ(0u, writer_->last_packet_size());
+  EXPECT_TRUE(connection_.HasQueuedData());
+
+  writer_->SetWritable();
+  connection_.OnCanWrite();
+  EXPECT_TRUE(writer_->version_negotiation_packet() != nullptr);
+
+  size_t num_versions = arraysize(kSupportedQuicVersions);
+  ASSERT_EQ(num_versions,
+            writer_->version_negotiation_packet()->versions.size());
+
+  // We expect all versions in kSupportedQuicVersions to be
+  // included in the packet.
+  for (size_t i = 0; i < num_versions; ++i) {
+    EXPECT_EQ(kSupportedQuicVersions[i],
+              writer_->version_negotiation_packet()->versions[i]);
+  }
+}
+
+TEST_P(QuicConnectionTest,
+       ServerSendsVersionNegotiationPacketSocketBlockedDataBuffered) {
+  connection_.SetSupportedVersions(QuicSupportedVersions());
+  framer_.set_version_for_tests(QUIC_VERSION_UNSUPPORTED);
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.packet_sequence_number = 12;
+  header.fec_group = 0;
+
+  QuicFrames frames;
+  QuicFrame frame(&frame1_);
+  frames.push_back(frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer_, header, frames).packet);
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+
+  framer_.set_version(version());
+  connection_.set_is_server(true);
+  BlockOnNextWrite();
+  writer_->set_is_write_blocked_data_buffered(true);
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+  EXPECT_EQ(0u, writer_->last_packet_size());
+  EXPECT_FALSE(connection_.HasQueuedData());
+}
+
+TEST_P(QuicConnectionTest, ClientHandlesVersionNegotiation) {
+  // Start out with some unsupported version.
+  QuicConnectionPeer::GetFramer(&connection_)->set_version_for_tests(
+      QUIC_VERSION_UNSUPPORTED);
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.packet_sequence_number = 12;
+  header.fec_group = 0;
+
+  QuicVersionVector supported_versions;
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    supported_versions.push_back(kSupportedQuicVersions[i]);
+  }
+
+  // Send a version negotiation packet.
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.BuildVersionNegotiationPacket(
+          header.public_header, supported_versions));
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+
+  // Now force another packet.  The connection should transition into
+  // NEGOTIATED_VERSION state and tell the packet creator to StopSendingVersion.
+  header.public_header.version_flag = false;
+  QuicFrames frames;
+  QuicFrame frame(&frame1_);
+  frames.push_back(frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer_, header, frames).packet);
+  encrypted.reset(framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+
+  ASSERT_FALSE(QuicPacketCreatorPeer::SendVersionInPacket(
+      QuicConnectionPeer::GetPacketCreator(&connection_)));
+}
+
+TEST_P(QuicConnectionTest, BadVersionNegotiation) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.packet_sequence_number = 12;
+  header.fec_group = 0;
+
+  QuicVersionVector supported_versions;
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    supported_versions.push_back(kSupportedQuicVersions[i]);
+  }
+
+  // Send a version negotiation packet with the version the client started with.
+  // It should be rejected.
+  EXPECT_CALL(visitor_,
+              OnConnectionClosed(QUIC_INVALID_VERSION_NEGOTIATION_PACKET,
+                                 false));
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.BuildVersionNegotiationPacket(
+          header.public_header, supported_versions));
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+}
+
+TEST_P(QuicConnectionTest, CheckSendStats) {
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  connection_.SendStreamDataWithString(3, "first", 0, !kFin, nullptr);
+  size_t first_packet_size = writer_->last_packet_size();
+
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  connection_.SendStreamDataWithString(5, "second", 0, !kFin, nullptr);
+  size_t second_packet_size = writer_->last_packet_size();
+
+  // 2 retransmissions due to rto, 1 due to explicit nack.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(3);
+
+  // Retransmit due to RTO.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+  connection_.GetRetransmissionAlarm()->Fire();
+
+  // Retransmit due to explicit nacks.
+  QuicAckFrame nack_three = InitAckFrame(4);
+  NackPacket(3, &nack_three);
+  NackPacket(1, &nack_three);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(1);
+  lost_packets.insert(3);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(visitor_, OnCanWrite()).Times(2);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, RevertRetransmissionTimeout());
+  ProcessAckPacket(&nack_three);
+
+  EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillOnce(
+      Return(QuicBandwidth::Zero()));
+
+  const uint32 kSlowStartThreshold = 23u;
+  EXPECT_CALL(*send_algorithm_, GetSlowStartThreshold()).WillOnce(
+      Return(kSlowStartThreshold));
+
+  const QuicConnectionStats& stats = connection_.GetStats();
+  EXPECT_EQ(3 * first_packet_size + 2 * second_packet_size - kQuicVersionSize,
+            stats.bytes_sent);
+  EXPECT_EQ(5u, stats.packets_sent);
+  EXPECT_EQ(2 * first_packet_size + second_packet_size - kQuicVersionSize,
+            stats.bytes_retransmitted);
+  EXPECT_EQ(3u, stats.packets_retransmitted);
+  EXPECT_EQ(1u, stats.rto_count);
+  EXPECT_EQ(kMaxPacketSize, stats.congestion_window);
+  EXPECT_EQ(kSlowStartThreshold, stats.slow_start_threshold);
+  EXPECT_EQ(kDefaultMaxPacketSize, stats.max_packet_size);
+}
+
+TEST_P(QuicConnectionTest, CheckReceiveStats) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  size_t received_bytes = 0;
+  received_bytes += ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+  received_bytes += ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+  // Should be counted against dropped packets.
+  received_bytes += ProcessDataPacket(3, 1, !kEntropyFlag);
+  received_bytes += ProcessFecPacket(4, 1, true, !kEntropyFlag, nullptr);
+
+  EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillOnce(
+      Return(QuicBandwidth::Zero()));
+  const uint32 kSlowStartThreshold = 23u;
+  EXPECT_CALL(*send_algorithm_, GetSlowStartThreshold()).WillOnce(
+      Return(kSlowStartThreshold));
+
+  const QuicConnectionStats& stats = connection_.GetStats();
+  EXPECT_EQ(received_bytes, stats.bytes_received);
+  EXPECT_EQ(4u, stats.packets_received);
+
+  EXPECT_EQ(1u, stats.packets_revived);
+  EXPECT_EQ(1u, stats.packets_dropped);
+
+  EXPECT_EQ(kSlowStartThreshold, stats.slow_start_threshold);
+}
+
+TEST_P(QuicConnectionTest, TestFecGroupLimits) {
+  // Create and return a group for 1.
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 1) != nullptr);
+
+  // Create and return a group for 2.
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) != nullptr);
+
+  // Create and return a group for 4.  This should remove 1 but not 2.
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 4) != nullptr);
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 1) == nullptr);
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) != nullptr);
+
+  // Create and return a group for 3.  This will kill off 2.
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 3) != nullptr);
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) == nullptr);
+
+  // Verify that adding 5 kills off 3, despite 4 being created before 3.
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 5) != nullptr);
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 4) != nullptr);
+  ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 3) == nullptr);
+}
+
+TEST_P(QuicConnectionTest, ProcessFramesIfPacketClosedConnection) {
+  // Construct a packet with stream frame and connection close frame.
+  header_.public_header.connection_id = connection_id_;
+  header_.packet_sequence_number = 1;
+  header_.public_header.reset_flag = false;
+  header_.public_header.version_flag = false;
+  header_.entropy_flag = false;
+  header_.fec_flag = false;
+  header_.fec_group = 0;
+
+  QuicConnectionCloseFrame qccf;
+  qccf.error_code = QUIC_PEER_GOING_AWAY;
+  QuicFrame close_frame(&qccf);
+  QuicFrame stream_frame(&frame1_);
+
+  QuicFrames frames;
+  frames.push_back(stream_frame);
+  frames.push_back(close_frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer_, header_, frames).packet);
+  EXPECT_TRUE(nullptr != packet.get());
+  scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+      ENCRYPTION_NONE, 1, *packet));
+
+  EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_PEER_GOING_AWAY, true));
+  EXPECT_CALL(visitor_, OnStreamFrames(_)).Times(1);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+}
+
+TEST_P(QuicConnectionTest, SelectMutualVersion) {
+  connection_.SetSupportedVersions(QuicSupportedVersions());
+  // Set the connection to speak the lowest quic version.
+  connection_.set_version(QuicVersionMin());
+  EXPECT_EQ(QuicVersionMin(), connection_.version());
+
+  // Pass in available versions which includes a higher mutually supported
+  // version.  The higher mutually supported version should be selected.
+  QuicVersionVector supported_versions;
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    supported_versions.push_back(kSupportedQuicVersions[i]);
+  }
+  EXPECT_TRUE(connection_.SelectMutualVersion(supported_versions));
+  EXPECT_EQ(QuicVersionMax(), connection_.version());
+
+  // Expect that the lowest version is selected.
+  // Ensure the lowest supported version is less than the max, unless they're
+  // the same.
+  EXPECT_LE(QuicVersionMin(), QuicVersionMax());
+  QuicVersionVector lowest_version_vector;
+  lowest_version_vector.push_back(QuicVersionMin());
+  EXPECT_TRUE(connection_.SelectMutualVersion(lowest_version_vector));
+  EXPECT_EQ(QuicVersionMin(), connection_.version());
+
+  // Shouldn't be able to find a mutually supported version.
+  QuicVersionVector unsupported_version;
+  unsupported_version.push_back(QUIC_VERSION_UNSUPPORTED);
+  EXPECT_FALSE(connection_.SelectMutualVersion(unsupported_version));
+}
+
+TEST_P(QuicConnectionTest, ConnectionCloseWhenWritable) {
+  EXPECT_FALSE(writer_->IsWriteBlocked());
+
+  // Send a packet.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(0u, connection_.NumQueuedPackets());
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+
+  TriggerConnectionClose();
+  EXPECT_EQ(2u, writer_->packets_write_attempts());
+}
+
+TEST_P(QuicConnectionTest, ConnectionCloseGettingWriteBlocked) {
+  BlockOnNextWrite();
+  TriggerConnectionClose();
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+}
+
+TEST_P(QuicConnectionTest, ConnectionCloseWhenWriteBlocked) {
+  BlockOnNextWrite();
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  EXPECT_EQ(1u, connection_.NumQueuedPackets());
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+  EXPECT_TRUE(writer_->IsWriteBlocked());
+  TriggerConnectionClose();
+  EXPECT_EQ(1u, writer_->packets_write_attempts());
+}
+
+TEST_P(QuicConnectionTest, AckNotifierTriggerCallback) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(new MockAckNotifierDelegate);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(_, _, _, _, _)).Times(1);
+
+  // Send some data, which will register the delegate to be notified.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, delegate.get());
+
+  // Process an ACK from the server which should trigger the callback.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame frame = InitAckFrame(1);
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, AckNotifierFailToTriggerCallback) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Create a delegate which we don't expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(new MockAckNotifierDelegate);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(_, _, _, _, _)).Times(0);
+
+  // Send some data, which will register the delegate to be notified. This will
+  // not be ACKed and so the delegate should never be called.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, delegate.get());
+
+  // Send some other data which we will ACK.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(1, "bar", 0, !kFin, nullptr);
+
+  // Now we receive ACK for packets 2 and 3, but importantly missing packet 1
+  // which we registered to be notified about.
+  QuicAckFrame frame = InitAckFrame(3);
+  NackPacket(1, &frame);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(1);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, AckNotifierCallbackAfterRetransmission) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(new MockAckNotifierDelegate);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(_, _, _, _, _)).Times(1);
+
+  // Send four packets, and register to be notified on ACK of packet 2.
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(3, "bar", 0, !kFin, delegate.get());
+  connection_.SendStreamDataWithString(3, "baz", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(3, "qux", 0, !kFin, nullptr);
+
+  // Now we receive ACK for packets 1, 3, and 4 and lose 2.
+  QuicAckFrame frame = InitAckFrame(4);
+  NackPacket(2, &frame);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(2);
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  ProcessAckPacket(&frame);
+
+  // Now we get an ACK for packet 5 (retransmitted packet 2), which should
+  // trigger the callback.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillRepeatedly(Return(SequenceNumberSet()));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame second_ack_frame = InitAckFrame(5);
+  ProcessAckPacket(&second_ack_frame);
+}
+
+// AckNotifierCallback is triggered by the ack of a packet that timed
+// out and was retransmitted, even though the retransmission has a
+// different sequence number.
+TEST_P(QuicConnectionTest, AckNotifierCallbackForAckAfterRTO) {
+  InSequence s;
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+      DefaultRetransmissionTime());
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, delegate.get());
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+
+  EXPECT_EQ(1u, writer_->header().packet_sequence_number);
+  EXPECT_EQ(default_retransmission_time,
+            connection_.GetRetransmissionAlarm()->deadline());
+  // Simulate the retransmission alarm firing.
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, 2u, _, _));
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(2u, writer_->header().packet_sequence_number);
+  // We do not raise the high water mark yet.
+  EXPECT_EQ(1u, stop_waiting()->least_unacked);
+
+  // Ack the original packet, which will revert the RTO.
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*delegate.get(), OnAckNotification(1, _, 1, _, _));
+  EXPECT_CALL(*send_algorithm_, RevertRetransmissionTimeout());
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame ack_frame = InitAckFrame(1);
+  ProcessAckPacket(&ack_frame);
+
+  // Delegate is not notified again when the retransmit is acked.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame second_ack_frame = InitAckFrame(2);
+  ProcessAckPacket(&second_ack_frame);
+}
+
+// AckNotifierCallback is triggered by the ack of a packet that was
+// previously nacked, even though the retransmission has a different
+// sequence number.
+TEST_P(QuicConnectionTest, AckNotifierCallbackForAckOfNackedPacket) {
+  InSequence s;
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  // Send four packets, and register to be notified on ACK of packet 2.
+  connection_.SendStreamDataWithString(3, "foo", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(3, "bar", 0, !kFin, delegate.get());
+  connection_.SendStreamDataWithString(3, "baz", 0, !kFin, nullptr);
+  connection_.SendStreamDataWithString(3, "qux", 0, !kFin, nullptr);
+
+  // Now we receive ACK for packets 1, 3, and 4 and lose 2.
+  QuicAckFrame frame = InitAckFrame(4);
+  NackPacket(2, &frame);
+  SequenceNumberSet lost_packets;
+  lost_packets.insert(2);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
+  ProcessAckPacket(&frame);
+
+  // Now we get an ACK for packet 2, which was previously nacked.
+  SequenceNumberSet no_lost_packets;
+  EXPECT_CALL(*delegate.get(), OnAckNotification(1, _, 1, _, _));
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(no_lost_packets));
+  QuicAckFrame second_ack_frame = InitAckFrame(4);
+  ProcessAckPacket(&second_ack_frame);
+
+  // Verify that the delegate is not notified again when the
+  // retransmit is acked.
+  EXPECT_CALL(*loss_algorithm_, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(no_lost_packets));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame third_ack_frame = InitAckFrame(5);
+  ProcessAckPacket(&third_ack_frame);
+}
+
+TEST_P(QuicConnectionTest, AckNotifierFECTriggerCallback) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new MockAckNotifierDelegate);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(_, _, _, _, _)).Times(1);
+
+  // Send some data, which will register the delegate to be notified.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, delegate.get());
+  connection_.SendStreamDataWithString(2, "bar", 0, !kFin, nullptr);
+
+  // Process an ACK from the server with a revived packet, which should trigger
+  // the callback.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+  QuicAckFrame frame = InitAckFrame(2);
+  NackPacket(1, &frame);
+  frame.revived_packets.insert(1);
+  ProcessAckPacket(&frame);
+  // If the ack is processed again, the notifier should not be called again.
+  ProcessAckPacket(&frame);
+}
+
+TEST_P(QuicConnectionTest, AckNotifierCallbackAfterFECRecovery) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(visitor_, OnCanWrite());
+
+  // Create a delegate which we expect to be called.
+  scoped_refptr<MockAckNotifierDelegate> delegate(new MockAckNotifierDelegate);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(_, _, _, _, _)).Times(1);
+
+  // Expect ACKs for 1 packet.
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _));
+
+  // Send one packet, and register to be notified on ACK.
+  connection_.SendStreamDataWithString(1, "foo", 0, !kFin, delegate.get());
+
+  // Ack packet gets dropped, but we receive an FEC packet that covers it.
+  // Should recover the Ack packet and trigger the notification callback.
+  QuicFrames frames;
+
+  QuicAckFrame ack_frame = InitAckFrame(1);
+  frames.push_back(QuicFrame(&ack_frame));
+
+  // Dummy stream frame to satisfy expectations set elsewhere.
+  frames.push_back(QuicFrame(&frame1_));
+
+  QuicPacketHeader ack_header;
+  ack_header.public_header.connection_id = connection_id_;
+  ack_header.public_header.reset_flag = false;
+  ack_header.public_header.version_flag = false;
+  ack_header.entropy_flag = !kEntropyFlag;
+  ack_header.fec_flag = true;
+  ack_header.packet_sequence_number = 1;
+  ack_header.is_in_fec_group = IN_FEC_GROUP;
+  ack_header.fec_group = 1;
+
+  QuicPacket* packet =
+      BuildUnsizedDataPacket(&framer_, ack_header, frames).packet;
+
+  // Take the packet which contains the ACK frame, and construct and deliver an
+  // FEC packet which allows the ACK packet to be recovered.
+  ProcessFecPacket(2, 1, true, !kEntropyFlag, packet);
+}
+
+TEST_P(QuicConnectionTest, NetworkChangeVisitorCallbacksChangeFecState) {
+  QuicPacketCreator* creator =
+      QuicConnectionPeer::GetPacketCreator(&connection_);
+  size_t max_packets_per_fec_group = creator->max_packets_per_fec_group();
+
+  QuicSentPacketManager::NetworkChangeVisitor* visitor =
+      QuicSentPacketManagerPeer::GetNetworkChangeVisitor(
+          QuicConnectionPeer::GetSentPacketManager(&connection_));
+  EXPECT_TRUE(visitor);
+
+  // Increase FEC group size by increasing congestion window to a large number.
+  visitor->OnCongestionWindowChange(1000 * kDefaultTCPMSS);
+  EXPECT_LT(max_packets_per_fec_group, creator->max_packets_per_fec_group());
+}
+
+class MockQuicConnectionDebugVisitor
+    : public QuicConnectionDebugVisitor {
+ public:
+  MOCK_METHOD1(OnFrameAddedToPacket,
+               void(const QuicFrame&));
+
+  MOCK_METHOD6(OnPacketSent,
+               void(QuicPacketSequenceNumber,
+                    QuicPacketSequenceNumber,
+                    EncryptionLevel,
+                    TransmissionType,
+                    const QuicEncryptedPacket&,
+                    WriteResult));
+
+  MOCK_METHOD3(OnPacketReceived,
+               void(const IPEndPoint&,
+                    const IPEndPoint&,
+                    const QuicEncryptedPacket&));
+
+  MOCK_METHOD1(OnProtocolVersionMismatch,
+               void(QuicVersion));
+
+  MOCK_METHOD1(OnPacketHeader,
+               void(const QuicPacketHeader& header));
+
+  MOCK_METHOD1(OnStreamFrame,
+               void(const QuicStreamFrame&));
+
+  MOCK_METHOD1(OnAckFrame,
+               void(const QuicAckFrame& frame));
+
+  MOCK_METHOD1(OnCongestionFeedbackFrame,
+               void(const QuicCongestionFeedbackFrame&));
+
+  MOCK_METHOD1(OnStopWaitingFrame,
+               void(const QuicStopWaitingFrame&));
+
+  MOCK_METHOD1(OnRstStreamFrame,
+               void(const QuicRstStreamFrame&));
+
+  MOCK_METHOD1(OnConnectionCloseFrame,
+               void(const QuicConnectionCloseFrame&));
+
+  MOCK_METHOD1(OnPublicResetPacket,
+               void(const QuicPublicResetPacket&));
+
+  MOCK_METHOD1(OnVersionNegotiationPacket,
+               void(const QuicVersionNegotiationPacket&));
+
+  MOCK_METHOD2(OnRevivedPacket,
+               void(const QuicPacketHeader&, StringPiece payload));
+};
+
+TEST_P(QuicConnectionTest, OnPacketHeaderDebugVisitor) {
+  QuicPacketHeader header;
+
+  MockQuicConnectionDebugVisitor* debug_visitor =
+      new MockQuicConnectionDebugVisitor();
+  connection_.set_debug_visitor(debug_visitor);
+  EXPECT_CALL(*debug_visitor, OnPacketHeader(Ref(header))).Times(1);
+  connection_.OnPacketHeader(header);
+}
+
+TEST_P(QuicConnectionTest, Pacing) {
+  TestConnection server(connection_id_, IPEndPoint(), helper_.get(),
+                        factory_, /* is_server= */ true, version());
+  TestConnection client(connection_id_, IPEndPoint(), helper_.get(),
+                        factory_, /* is_server= */ false, version());
+  EXPECT_FALSE(client.sent_packet_manager().using_pacing());
+  EXPECT_FALSE(server.sent_packet_manager().using_pacing());
+}
+
+TEST_P(QuicConnectionTest, ControlFramesInstigateAcks) {
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+  // Send a WINDOW_UPDATE frame.
+  QuicWindowUpdateFrame window_update;
+  window_update.stream_id = 3;
+  window_update.byte_offset = 1234;
+  EXPECT_CALL(visitor_, OnWindowUpdateFrames(_));
+  ProcessFramePacket(QuicFrame(&window_update));
+
+  // Ensure that this has caused the ACK alarm to be set.
+  QuicAlarm* ack_alarm = QuicConnectionPeer::GetAckAlarm(&connection_);
+  EXPECT_TRUE(ack_alarm->IsSet());
+
+  // Cancel alarm, and try again with BLOCKED frame.
+  ack_alarm->Cancel();
+  QuicBlockedFrame blocked;
+  blocked.stream_id = 3;
+  EXPECT_CALL(visitor_, OnBlockedFrames(_));
+  ProcessFramePacket(QuicFrame(&blocked));
+  EXPECT_TRUE(ack_alarm->IsSet());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_crypto_client_stream.cc b/net/quic/quic_crypto_client_stream.cc
new file mode 100644
index 0000000..1c41e0a
--- /dev/null
+++ b/net/quic/quic_crypto_client_stream.cc
@@ -0,0 +1,601 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_client_stream.h"
+
+#include "base/metrics/histogram.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/quic_client_session_base.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+QuicCryptoClientStream::ChannelIDSourceCallbackImpl::
+ChannelIDSourceCallbackImpl(QuicCryptoClientStream* stream)
+    : stream_(stream) {}
+
+QuicCryptoClientStream::ChannelIDSourceCallbackImpl::
+~ChannelIDSourceCallbackImpl() {}
+
+void QuicCryptoClientStream::ChannelIDSourceCallbackImpl::Run(
+    scoped_ptr<ChannelIDKey>* channel_id_key) {
+  if (stream_ == nullptr) {
+    return;
+  }
+
+  stream_->channel_id_key_.reset(channel_id_key->release());
+  stream_->channel_id_source_callback_run_ = true;
+  stream_->channel_id_source_callback_ = nullptr;
+  stream_->DoHandshakeLoop(nullptr);
+
+  // The ChannelIDSource owns this object and will delete it when this method
+  // returns.
+}
+
+void QuicCryptoClientStream::ChannelIDSourceCallbackImpl::Cancel() {
+  stream_ = nullptr;
+}
+
+QuicCryptoClientStream::ProofVerifierCallbackImpl::ProofVerifierCallbackImpl(
+    QuicCryptoClientStream* stream)
+    : stream_(stream) {}
+
+QuicCryptoClientStream::ProofVerifierCallbackImpl::
+~ProofVerifierCallbackImpl() {}
+
+void QuicCryptoClientStream::ProofVerifierCallbackImpl::Run(
+    bool ok,
+    const string& error_details,
+    scoped_ptr<ProofVerifyDetails>* details) {
+  if (stream_ == nullptr) {
+    return;
+  }
+
+  stream_->verify_ok_ = ok;
+  stream_->verify_error_details_ = error_details;
+  stream_->verify_details_.reset(details->release());
+  stream_->proof_verify_callback_ = nullptr;
+  stream_->DoHandshakeLoop(nullptr);
+
+  // The ProofVerifier owns this object and will delete it when this method
+  // returns.
+}
+
+void QuicCryptoClientStream::ProofVerifierCallbackImpl::Cancel() {
+  stream_ = nullptr;
+}
+
+QuicCryptoClientStream::QuicCryptoClientStream(
+    const QuicServerId& server_id,
+    QuicClientSessionBase* session,
+    ProofVerifyContext* verify_context,
+    QuicCryptoClientConfig* crypto_config)
+    : QuicCryptoStream(session),
+      next_state_(STATE_IDLE),
+      num_client_hellos_(0),
+      crypto_config_(crypto_config),
+      server_id_(server_id),
+      generation_counter_(0),
+      channel_id_sent_(false),
+      channel_id_source_callback_run_(false),
+      channel_id_source_callback_(nullptr),
+      verify_context_(verify_context),
+      proof_verify_callback_(nullptr) {}
+
+QuicCryptoClientStream::~QuicCryptoClientStream() {
+  if (channel_id_source_callback_) {
+    channel_id_source_callback_->Cancel();
+  }
+  if (proof_verify_callback_) {
+    proof_verify_callback_->Cancel();
+  }
+}
+
+void QuicCryptoClientStream::OnHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  QuicCryptoStream::OnHandshakeMessage(message);
+
+  if (message.tag() == kSCUP) {
+    if (!handshake_confirmed()) {
+      CloseConnection(QUIC_CRYPTO_UPDATE_BEFORE_HANDSHAKE_COMPLETE);
+      return;
+    }
+
+    // |message| is an update from the server, so we treat it differently from a
+    // handshake message.
+    HandleServerConfigUpdateMessage(message);
+    return;
+  }
+
+  // Do not process handshake messages after the handshake is confirmed.
+  if (handshake_confirmed()) {
+    CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+    return;
+  }
+
+  DoHandshakeLoop(&message);
+}
+
+bool QuicCryptoClientStream::CryptoConnect() {
+  next_state_ = STATE_INITIALIZE;
+  DoHandshakeLoop(nullptr);
+  return true;
+}
+
+int QuicCryptoClientStream::num_sent_client_hellos() const {
+  return num_client_hellos_;
+}
+
+bool QuicCryptoClientStream::WasChannelIDSent() const {
+  return channel_id_sent_;
+}
+
+bool QuicCryptoClientStream::WasChannelIDSourceCallbackRun() const {
+  return channel_id_source_callback_run_;
+}
+
+void QuicCryptoClientStream::HandleServerConfigUpdateMessage(
+    const CryptoHandshakeMessage& server_config_update) {
+  DCHECK(server_config_update.tag() == kSCUP);
+  string error_details;
+  QuicCryptoClientConfig::CachedState* cached =
+      crypto_config_->LookupOrCreate(server_id_);
+  QuicErrorCode error = crypto_config_->ProcessServerConfigUpdate(
+      server_config_update,
+      session()->connection()->clock()->WallNow(),
+      cached,
+      &crypto_negotiated_params_,
+      &error_details);
+
+  if (error != QUIC_NO_ERROR) {
+    CloseConnectionWithDetails(
+        error, "Server config update invalid: " + error_details);
+    return;
+  }
+
+  DCHECK(handshake_confirmed());
+  if (proof_verify_callback_) {
+    proof_verify_callback_->Cancel();
+  }
+  next_state_ = STATE_INITIALIZE_SCUP;
+  DoHandshakeLoop(nullptr);
+}
+
+// kMaxClientHellos is the maximum number of times that we'll send a client
+// hello. The value 3 accounts for:
+//   * One failure due to an incorrect or missing source-address token.
+//   * One failure due the server's certificate chain being unavailible and the
+//     server being unwilling to send it without a valid source-address token.
+static const int kMaxClientHellos = 3;
+
+void QuicCryptoClientStream::DoHandshakeLoop(
+    const CryptoHandshakeMessage* in) {
+  QuicCryptoClientConfig::CachedState* cached =
+      crypto_config_->LookupOrCreate(server_id_);
+
+  QuicAsyncStatus rv = QUIC_SUCCESS;
+  do {
+    CHECK_NE(STATE_NONE, next_state_);
+    const State state = next_state_;
+    next_state_ = STATE_IDLE;
+    rv = QUIC_SUCCESS;
+    switch (state) {
+      case STATE_INITIALIZE:
+        DoInitialize(cached);
+        break;
+      case STATE_SEND_CHLO:
+        DoSendCHLO(in, cached);
+        return;
+      case STATE_RECV_REJ:
+        DoReceiveREJ(in, cached);
+        break;
+      case STATE_VERIFY_PROOF:
+        rv = DoVerifyProof(cached);
+        break;
+      case STATE_VERIFY_PROOF_COMPLETE:
+        DoVerifyProofComplete(cached);
+        break;
+      case STATE_GET_CHANNEL_ID:
+        rv = DoGetChannelID(cached);
+        break;
+      case STATE_GET_CHANNEL_ID_COMPLETE:
+        DoGetChannelIDComplete();
+        break;
+      case STATE_RECV_SHLO:
+        DoReceiveSHLO(in, cached);
+        break;
+      case STATE_IDLE:
+        // This means that the peer sent us a message that we weren't expecting.
+        CloseConnection(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+        return;
+      case STATE_INITIALIZE_SCUP:
+        DoInitializeServerConfigUpdate(cached);
+        break;
+      case STATE_NONE:
+        NOTREACHED();
+        return;  // We are done.
+    }
+  } while (rv != QUIC_PENDING && next_state_ != STATE_NONE);
+}
+
+void QuicCryptoClientStream::DoInitialize(
+    QuicCryptoClientConfig::CachedState* cached) {
+  if (!cached->IsEmpty() && !cached->signature().empty() &&
+      server_id_.is_https()) {
+    // Note that we verify the proof even if the cached proof is valid.
+    // This allows us to respond to CA trust changes or certificate
+    // expiration because it may have been a while since we last verified
+    // the proof.
+    DCHECK(crypto_config_->proof_verifier());
+    // If the cached state needs to be verified, do it now.
+    next_state_ = STATE_VERIFY_PROOF;
+  } else {
+    next_state_ = STATE_GET_CHANNEL_ID;
+  }
+}
+
+void QuicCryptoClientStream::DoSendCHLO(
+    const CryptoHandshakeMessage* in,
+    QuicCryptoClientConfig::CachedState* cached) {
+  // Send the client hello in plaintext.
+  session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_NONE);
+  if (num_client_hellos_ > kMaxClientHellos) {
+    CloseConnection(QUIC_CRYPTO_TOO_MANY_REJECTS);
+    return;
+  }
+  num_client_hellos_++;
+
+  CryptoHandshakeMessage out;
+  if (!cached->IsComplete(session()->connection()->clock()->WallNow())) {
+    crypto_config_->FillInchoateClientHello(
+        server_id_,
+        session()->connection()->supported_versions().front(),
+        cached, &crypto_negotiated_params_, &out);
+    // Pad the inchoate client hello to fill up a packet.
+    const size_t kFramingOverhead = 50;  // A rough estimate.
+    const size_t max_packet_size =
+        session()->connection()->max_packet_length();
+    if (max_packet_size <= kFramingOverhead) {
+      DLOG(DFATAL) << "max_packet_length (" << max_packet_size
+                   << ") has no room for framing overhead.";
+      CloseConnection(QUIC_INTERNAL_ERROR);
+      return;
+    }
+    if (kClientHelloMinimumSize > max_packet_size - kFramingOverhead) {
+      DLOG(DFATAL) << "Client hello won't fit in a single packet.";
+      CloseConnection(QUIC_INTERNAL_ERROR);
+      return;
+    }
+    out.set_minimum_size(max_packet_size - kFramingOverhead);
+    next_state_ = STATE_RECV_REJ;
+    SendHandshakeMessage(out);
+    return;
+  }
+
+  session()->config()->ToHandshakeMessage(&out);
+  string error_details;
+  QuicErrorCode error = crypto_config_->FillClientHello(
+      server_id_,
+      session()->connection()->connection_id(),
+      session()->connection()->supported_versions().front(),
+      cached,
+      session()->connection()->clock()->WallNow(),
+      session()->connection()->random_generator(),
+      channel_id_key_.get(),
+      &crypto_negotiated_params_,
+      &out,
+      &error_details);
+  if (error != QUIC_NO_ERROR) {
+    // Flush the cached config so that, if it's bad, the server has a
+    // chance to send us another in the future.
+    cached->InvalidateServerConfig();
+    CloseConnectionWithDetails(error, error_details);
+    return;
+  }
+  channel_id_sent_ = (channel_id_key_.get() != nullptr);
+  if (cached->proof_verify_details()) {
+    client_session()->OnProofVerifyDetailsAvailable(
+        *cached->proof_verify_details());
+  }
+  next_state_ = STATE_RECV_SHLO;
+  SendHandshakeMessage(out);
+  // Be prepared to decrypt with the new server write key.
+  session()->connection()->SetAlternativeDecrypter(
+      crypto_negotiated_params_.initial_crypters.decrypter.release(),
+      ENCRYPTION_INITIAL,
+      true /* latch once used */);
+  // Send subsequent packets under encryption on the assumption that the
+  // server will accept the handshake.
+  session()->connection()->SetEncrypter(
+      ENCRYPTION_INITIAL,
+      crypto_negotiated_params_.initial_crypters.encrypter.release());
+  session()->connection()->SetDefaultEncryptionLevel(
+      ENCRYPTION_INITIAL);
+  if (!encryption_established_) {
+    encryption_established_ = true;
+    session()->OnCryptoHandshakeEvent(
+        QuicSession::ENCRYPTION_FIRST_ESTABLISHED);
+  } else {
+    session()->OnCryptoHandshakeEvent(
+        QuicSession::ENCRYPTION_REESTABLISHED);
+  }
+}
+
+void QuicCryptoClientStream::DoReceiveREJ(
+    const CryptoHandshakeMessage* in,
+    QuicCryptoClientConfig::CachedState* cached) {
+  // We sent a dummy CHLO because we didn't have enough information to
+  // perform a handshake, or we sent a full hello that the server
+  // rejected. Here we hope to have a REJ that contains the information
+  // that we need.
+  if (in->tag() != kREJ) {
+    next_state_ = STATE_NONE;
+    CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
+                               "Expected REJ");
+    return;
+  }
+  string error_details;
+  QuicErrorCode error = crypto_config_->ProcessRejection(
+      *in, session()->connection()->clock()->WallNow(), cached,
+      server_id_.is_https(), &crypto_negotiated_params_, &error_details);
+  if (error != QUIC_NO_ERROR) {
+    next_state_ = STATE_NONE;
+    CloseConnectionWithDetails(error, error_details);
+    return;
+  }
+  if (!cached->proof_valid()) {
+    if (!server_id_.is_https()) {
+      // We don't check the certificates for insecure QUIC connections.
+      SetCachedProofValid(cached);
+    } else if (!cached->signature().empty()) {
+      // Note that we only verify the proof if the cached proof is not
+      // valid. If the cached proof is valid here, someone else must have
+      // just added the server config to the cache and verified the proof,
+      // so we can assume no CA trust changes or certificate expiration
+      // has happened since then.
+      next_state_ = STATE_VERIFY_PROOF;
+      return;
+    }
+  }
+  next_state_ = STATE_GET_CHANNEL_ID;
+}
+
+QuicAsyncStatus QuicCryptoClientStream::DoVerifyProof(
+    QuicCryptoClientConfig::CachedState* cached) {
+  ProofVerifier* verifier = crypto_config_->proof_verifier();
+  DCHECK(verifier);
+  next_state_ = STATE_VERIFY_PROOF_COMPLETE;
+  generation_counter_ = cached->generation_counter();
+
+  ProofVerifierCallbackImpl* proof_verify_callback =
+      new ProofVerifierCallbackImpl(this);
+
+  verify_ok_ = false;
+
+  QuicAsyncStatus status = verifier->VerifyProof(
+      server_id_.host(),
+      cached->server_config(),
+      cached->certs(),
+      cached->signature(),
+      verify_context_.get(),
+      &verify_error_details_,
+      &verify_details_,
+      proof_verify_callback);
+
+  switch (status) {
+    case QUIC_PENDING:
+      proof_verify_callback_ = proof_verify_callback;
+      DVLOG(1) << "Doing VerifyProof";
+      break;
+    case QUIC_FAILURE:
+      delete proof_verify_callback;
+      break;
+    case QUIC_SUCCESS:
+      delete proof_verify_callback;
+      verify_ok_ = true;
+      break;
+  }
+  return status;
+}
+
+void QuicCryptoClientStream::DoVerifyProofComplete(
+    QuicCryptoClientConfig::CachedState* cached) {
+  if (!verify_ok_) {
+    next_state_ = STATE_NONE;
+    client_session()->OnProofVerifyDetailsAvailable(*verify_details_);
+    UMA_HISTOGRAM_BOOLEAN("Net.QuicVerifyProofFailed.HandshakeConfirmed",
+                          handshake_confirmed());
+    CloseConnectionWithDetails(
+        QUIC_PROOF_INVALID, "Proof invalid: " + verify_error_details_);
+    return;
+  }
+
+  // Check if generation_counter has changed between STATE_VERIFY_PROOF and
+  // STATE_VERIFY_PROOF_COMPLETE state changes.
+  if (generation_counter_ != cached->generation_counter()) {
+    next_state_ = STATE_VERIFY_PROOF;
+  } else {
+    SetCachedProofValid(cached);
+    cached->SetProofVerifyDetails(verify_details_.release());
+    if (!handshake_confirmed()) {
+      next_state_ = STATE_GET_CHANNEL_ID;
+    } else {
+      next_state_ = STATE_NONE;
+    }
+  }
+}
+
+QuicAsyncStatus QuicCryptoClientStream::DoGetChannelID(
+    QuicCryptoClientConfig::CachedState* cached) {
+  next_state_ = STATE_GET_CHANNEL_ID_COMPLETE;
+  channel_id_key_.reset();
+  if (!RequiresChannelID(cached)) {
+    next_state_ = STATE_SEND_CHLO;
+    return QUIC_SUCCESS;
+  }
+
+  ChannelIDSourceCallbackImpl* channel_id_source_callback =
+      new ChannelIDSourceCallbackImpl(this);
+  QuicAsyncStatus status =
+      crypto_config_->channel_id_source()->GetChannelIDKey(
+          server_id_.host(), &channel_id_key_,
+          channel_id_source_callback);
+
+  switch (status) {
+    case QUIC_PENDING:
+      channel_id_source_callback_ = channel_id_source_callback;
+      DVLOG(1) << "Looking up channel ID";
+      break;
+    case QUIC_FAILURE:
+      next_state_ = STATE_NONE;
+      delete channel_id_source_callback;
+      CloseConnectionWithDetails(QUIC_INVALID_CHANNEL_ID_SIGNATURE,
+                                 "Channel ID lookup failed");
+      break;
+    case QUIC_SUCCESS:
+      delete channel_id_source_callback;
+      break;
+  }
+  return status;
+}
+
+void QuicCryptoClientStream::DoGetChannelIDComplete() {
+  if (!channel_id_key_.get()) {
+    next_state_ = STATE_NONE;
+    CloseConnectionWithDetails(QUIC_INVALID_CHANNEL_ID_SIGNATURE,
+                               "Channel ID lookup failed");
+    return;
+  }
+  next_state_ = STATE_SEND_CHLO;
+}
+
+void QuicCryptoClientStream::DoReceiveSHLO(
+    const CryptoHandshakeMessage* in,
+    QuicCryptoClientConfig::CachedState* cached) {
+  next_state_ = STATE_NONE;
+  // We sent a CHLO that we expected to be accepted and now we're hoping
+  // for a SHLO from the server to confirm that.
+  if (in->tag() == kREJ) {
+    // alternative_decrypter will be nullptr if the original alternative
+    // decrypter latched and became the primary decrypter. That happens
+    // if we received a message encrypted with the INITIAL key.
+    if (session()->connection()->alternative_decrypter() == nullptr) {
+      // The rejection was sent encrypted!
+      CloseConnectionWithDetails(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT,
+                                 "encrypted REJ message");
+      return;
+    }
+    next_state_ = STATE_RECV_REJ;
+    return;
+  }
+
+  if (in->tag() != kSHLO) {
+    CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
+                               "Expected SHLO or REJ");
+    return;
+  }
+
+  // alternative_decrypter will be nullptr if the original alternative
+  // decrypter latched and became the primary decrypter. That happens
+  // if we received a message encrypted with the INITIAL key.
+  if (session()->connection()->alternative_decrypter() != nullptr) {
+    // The server hello was sent without encryption.
+    CloseConnectionWithDetails(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT,
+                               "unencrypted SHLO message");
+    return;
+  }
+
+  string error_details;
+  QuicErrorCode error = crypto_config_->ProcessServerHello(
+      *in, session()->connection()->connection_id(),
+      session()->connection()->server_supported_versions(),
+      cached, &crypto_negotiated_params_, &error_details);
+
+  if (error != QUIC_NO_ERROR) {
+    CloseConnectionWithDetails(error, "Server hello invalid: " + error_details);
+    return;
+  }
+  error = session()->config()->ProcessPeerHello(*in, SERVER, &error_details);
+  if (error != QUIC_NO_ERROR) {
+    CloseConnectionWithDetails(error, "Server hello invalid: " + error_details);
+    return;
+  }
+  session()->OnConfigNegotiated();
+
+  CrypterPair* crypters = &crypto_negotiated_params_.forward_secure_crypters;
+  // TODO(agl): we don't currently latch this decrypter because the idea
+  // has been floated that the server shouldn't send packets encrypted
+  // with the FORWARD_SECURE key until it receives a FORWARD_SECURE
+  // packet from the client.
+  session()->connection()->SetAlternativeDecrypter(
+      crypters->decrypter.release(), ENCRYPTION_FORWARD_SECURE,
+      false /* don't latch */);
+  session()->connection()->SetEncrypter(
+      ENCRYPTION_FORWARD_SECURE, crypters->encrypter.release());
+  session()->connection()->SetDefaultEncryptionLevel(
+      ENCRYPTION_FORWARD_SECURE);
+
+  handshake_confirmed_ = true;
+  session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+  session()->connection()->OnHandshakeComplete();
+}
+
+void QuicCryptoClientStream::DoInitializeServerConfigUpdate(
+    QuicCryptoClientConfig::CachedState* cached) {
+  bool update_ignored = false;
+  if (!server_id_.is_https()) {
+    // We don't check the certificates for insecure QUIC connections.
+    SetCachedProofValid(cached);
+    next_state_ = STATE_NONE;
+  } else if (!cached->IsEmpty() && !cached->signature().empty()) {
+    // Note that we verify the proof even if the cached proof is valid.
+    DCHECK(crypto_config_->proof_verifier());
+    next_state_ = STATE_VERIFY_PROOF;
+  } else {
+    update_ignored = true;
+    next_state_ = STATE_NONE;
+  }
+  UMA_HISTOGRAM_COUNTS("Net.QuicNumServerConfig.UpdateMessagesIgnored",
+                       update_ignored);
+}
+
+void QuicCryptoClientStream::SetCachedProofValid(
+    QuicCryptoClientConfig::CachedState* cached) {
+  cached->SetProofValid();
+  client_session()->OnProofValid(*cached);
+}
+
+bool QuicCryptoClientStream::RequiresChannelID(
+    QuicCryptoClientConfig::CachedState* cached) {
+  if (!server_id_.is_https() ||
+      server_id_.privacy_mode() == PRIVACY_MODE_ENABLED ||
+      !crypto_config_->channel_id_source()) {
+    return false;
+  }
+  const CryptoHandshakeMessage* scfg = cached->GetServerConfig();
+  if (!scfg) {  // scfg may be null when we send an inchoate CHLO.
+    return false;
+  }
+  const QuicTag* their_proof_demands;
+  size_t num_their_proof_demands;
+  if (scfg->GetTaglist(kPDMD, &their_proof_demands,
+                       &num_their_proof_demands) != QUIC_NO_ERROR) {
+    return false;
+  }
+  for (size_t i = 0; i < num_their_proof_demands; i++) {
+    if (their_proof_demands[i] == kCHID) {
+      return true;
+    }
+  }
+  return false;
+}
+
+QuicClientSessionBase* QuicCryptoClientStream::client_session() {
+  return reinterpret_cast<QuicClientSessionBase*>(session());
+}
+
+}  // namespace net
diff --git a/net/quic/quic_crypto_client_stream.h b/net/quic/quic_crypto_client_stream.h
new file mode 100644
index 0000000..96d2556
--- /dev/null
+++ b/net/quic/quic_crypto_client_stream.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
+
+#include <string>
+
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/crypto/quic_crypto_client_config.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_server_id.h"
+
+namespace net {
+
+class QuicClientSessionBase;
+
+namespace test {
+class CryptoTestUtils;
+class QuicClientSessionPeer;
+}  // namespace test
+
+class NET_EXPORT_PRIVATE QuicCryptoClientStream : public QuicCryptoStream {
+ public:
+  QuicCryptoClientStream(const QuicServerId& server_id,
+                         QuicClientSessionBase* session,
+                         ProofVerifyContext* verify_context,
+                         QuicCryptoClientConfig* crypto_config);
+  virtual ~QuicCryptoClientStream();
+
+  // CryptoFramerVisitorInterface implementation
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+
+  // Performs a crypto handshake with the server. Returns true if the crypto
+  // handshake is started successfully.
+  // TODO(agl): this should probably return void.
+  virtual bool CryptoConnect();
+
+  // num_sent_client_hellos returns the number of client hello messages that
+  // have been sent. If the handshake has completed then this is one greater
+  // than the number of round-trips needed for the handshake.
+  int num_sent_client_hellos() const;
+
+  // Returns true if a channel ID was sent on this connection.
+  bool WasChannelIDSent() const;
+
+  // Returns true if our ChannelIDSourceCallback was run, which implies the
+  // ChannelIDSource operated asynchronously. Intended for testing.
+  bool WasChannelIDSourceCallbackRun() const;
+
+ private:
+  // ChannelIDSourceCallbackImpl is passed as the callback method to
+  // GetChannelIDKey. The ChannelIDSource calls this class with the result of
+  // channel ID lookup when lookup is performed asynchronously.
+  class ChannelIDSourceCallbackImpl : public ChannelIDSourceCallback {
+   public:
+    explicit ChannelIDSourceCallbackImpl(QuicCryptoClientStream* stream);
+    virtual ~ChannelIDSourceCallbackImpl();
+
+    // ChannelIDSourceCallback interface.
+    virtual void Run(scoped_ptr<ChannelIDKey>* channel_id_key) OVERRIDE;
+
+    // Cancel causes any future callbacks to be ignored. It must be called on
+    // the same thread as the callback will be made on.
+    void Cancel();
+
+   private:
+    QuicCryptoClientStream* stream_;
+  };
+
+  // ProofVerifierCallbackImpl is passed as the callback method to VerifyProof.
+  // The ProofVerifier calls this class with the result of proof verification
+  // when verification is performed asynchronously.
+  class ProofVerifierCallbackImpl : public ProofVerifierCallback {
+   public:
+    explicit ProofVerifierCallbackImpl(QuicCryptoClientStream* stream);
+    virtual ~ProofVerifierCallbackImpl();
+
+    // ProofVerifierCallback interface.
+    virtual void Run(bool ok,
+                     const string& error_details,
+                     scoped_ptr<ProofVerifyDetails>* details) OVERRIDE;
+
+    // Cancel causes any future callbacks to be ignored. It must be called on
+    // the same thread as the callback will be made on.
+    void Cancel();
+
+   private:
+    QuicCryptoClientStream* stream_;
+  };
+
+  friend class test::CryptoTestUtils;
+  friend class test::QuicClientSessionPeer;
+
+  enum State {
+    STATE_IDLE,
+    STATE_INITIALIZE,
+    STATE_SEND_CHLO,
+    STATE_RECV_REJ,
+    STATE_VERIFY_PROOF,
+    STATE_VERIFY_PROOF_COMPLETE,
+    STATE_GET_CHANNEL_ID,
+    STATE_GET_CHANNEL_ID_COMPLETE,
+    STATE_RECV_SHLO,
+    STATE_INITIALIZE_SCUP,
+    STATE_NONE,
+  };
+
+  // Handles new server config and optional source-address token provided by the
+  // server during a connection.
+  void HandleServerConfigUpdateMessage(
+      const CryptoHandshakeMessage& server_config_update);
+
+  // DoHandshakeLoop performs a step of the handshake state machine. Note that
+  // |in| may be nullptr if the call did not result from a received message.
+  void DoHandshakeLoop(const CryptoHandshakeMessage* in);
+
+  // Start the handshake process.
+  void DoInitialize(QuicCryptoClientConfig::CachedState* cached);
+
+  // Send either InchoateClientHello or ClientHello message to the server.
+  void DoSendCHLO(const CryptoHandshakeMessage* in,
+                  QuicCryptoClientConfig::CachedState* cached);
+
+  // Process REJ message from the server.
+  void DoReceiveREJ(const CryptoHandshakeMessage* in,
+                    QuicCryptoClientConfig::CachedState* cached);
+
+  // Start the proof verification process. Returns the QuicAsyncStatus returned
+  // by the ProofVerifier's VerifyProof.
+  QuicAsyncStatus DoVerifyProof(QuicCryptoClientConfig::CachedState* cached);
+
+  // If proof is valid then it sets the proof as valid (which persists the
+  // server config). If not, it closes the connection.
+  void DoVerifyProofComplete(QuicCryptoClientConfig::CachedState* cached);
+
+  // Start the look up of Channel ID process. Returns either QUIC_SUCCESS if
+  // RequiresChannelID returns false or QuicAsyncStatus returned by
+  // GetChannelIDKey.
+  QuicAsyncStatus DoGetChannelID(QuicCryptoClientConfig::CachedState* cached);
+
+  // If there is no channel ID, then close the connection otherwise transtion to
+  // STATE_SEND_CHLO state.
+  void DoGetChannelIDComplete();
+
+  // Process SHLO message from the server.
+  void DoReceiveSHLO(const CryptoHandshakeMessage* in,
+                     QuicCryptoClientConfig::CachedState* cached);
+
+  // Start the proof verification if |server_id_| is https and |cached| has
+  // signature.
+  void DoInitializeServerConfigUpdate(
+      QuicCryptoClientConfig::CachedState* cached);
+
+  // Called to set the proof of |cached| valid.  Also invokes the session's
+  // OnProofValid() method.
+  void SetCachedProofValid(QuicCryptoClientConfig::CachedState* cached);
+
+  // Returns true if the server crypto config in |cached| requires a ChannelID
+  // and the client config settings also allow sending a ChannelID.
+  bool RequiresChannelID(QuicCryptoClientConfig::CachedState* cached);
+
+  QuicClientSessionBase* client_session();
+
+  State next_state_;
+  // num_client_hellos_ contains the number of client hello messages that this
+  // connection has sent.
+  int num_client_hellos_;
+
+  QuicCryptoClientConfig* const crypto_config_;
+
+  // Client's connection nonce (4-byte timestamp + 28 random bytes)
+  std::string nonce_;
+  // Server's (hostname, port, is_https, privacy_mode) tuple.
+  const QuicServerId server_id_;
+
+  // Generation counter from QuicCryptoClientConfig's CachedState.
+  uint64 generation_counter_;
+
+  // True if a channel ID was sent.
+  bool channel_id_sent_;
+
+  // True if channel_id_source_callback_ was run.
+  bool channel_id_source_callback_run_;
+
+  // channel_id_source_callback_ contains the callback object that we passed
+  // to an asynchronous channel ID lookup. The ChannelIDSource owns this
+  // object.
+  ChannelIDSourceCallbackImpl* channel_id_source_callback_;
+
+  // These members are used to store the result of an asynchronous channel ID
+  // lookup. These members must not be used after
+  // STATE_GET_CHANNEL_ID_COMPLETE.
+  scoped_ptr<ChannelIDKey> channel_id_key_;
+
+  // verify_context_ contains the context object that we pass to asynchronous
+  // proof verifications.
+  scoped_ptr<ProofVerifyContext> verify_context_;
+
+  // proof_verify_callback_ contains the callback object that we passed to an
+  // asynchronous proof verification. The ProofVerifier owns this object.
+  ProofVerifierCallbackImpl* proof_verify_callback_;
+
+  // These members are used to store the result of an asynchronous proof
+  // verification. These members must not be used after
+  // STATE_VERIFY_PROOF_COMPLETE.
+  bool verify_ok_;
+  string verify_error_details_;
+  scoped_ptr<ProofVerifyDetails> verify_details_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoClientStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
diff --git a/net/quic/quic_crypto_client_stream_factory.h b/net/quic/quic_crypto_client_stream_factory.h
new file mode 100644
index 0000000..293fa82
--- /dev/null
+++ b/net/quic/quic_crypto_client_stream_factory.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
+#define NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class QuicClientSession;
+class QuicCryptoClientStream;
+class QuicServerId;
+
+// An interface used to instantiate QuicCryptoClientStream objects. Used to
+// facilitate testing code with mock implementations.
+class NET_EXPORT QuicCryptoClientStreamFactory {
+ public:
+  virtual ~QuicCryptoClientStreamFactory() {}
+
+  virtual QuicCryptoClientStream* CreateQuicCryptoClientStream(
+      const QuicServerId& server_id,
+      QuicClientSession* session,
+      QuicCryptoClientConfig* crypto_config) = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
diff --git a/net/quic/quic_crypto_client_stream_test.cc b/net/quic/quic_crypto_client_stream_test.cc
new file mode 100644
index 0000000..5a8b08c
--- /dev/null
+++ b/net/quic/quic_crypto_client_stream_test.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_client_stream.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_id.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "example.com";
+const uint16 kServerPort = 80;
+
+class QuicCryptoClientStreamTest : public ::testing::Test {
+ public:
+  QuicCryptoClientStreamTest()
+      : connection_(new PacketSavingConnection(false)),
+        session_(new TestClientSession(connection_, DefaultQuicConfig())),
+        server_id_(kServerHostname, kServerPort, false, PRIVACY_MODE_DISABLED),
+        stream_(new QuicCryptoClientStream(server_id_, session_.get(), nullptr,
+                                           &crypto_config_)) {
+    session_->SetCryptoStream(stream_.get());
+    session_->config()->SetDefaults();
+    crypto_config_.SetDefaults();
+  }
+
+  void CompleteCryptoHandshake() {
+    EXPECT_TRUE(stream_->CryptoConnect());
+    CryptoTestUtils::HandshakeWithFakeServer(connection_, stream_.get());
+  }
+
+  void ConstructHandshakeMessage() {
+    CryptoFramer framer;
+    message_data_.reset(framer.ConstructHandshakeMessage(message_));
+  }
+
+  PacketSavingConnection* connection_;
+  scoped_ptr<TestClientSession> session_;
+  QuicServerId server_id_;
+  scoped_ptr<QuicCryptoClientStream> stream_;
+  CryptoHandshakeMessage message_;
+  scoped_ptr<QuicData> message_data_;
+  QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(QuicCryptoClientStreamTest, NotInitiallyConected) {
+  EXPECT_FALSE(stream_->encryption_established());
+  EXPECT_FALSE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, ConnectedAfterSHLO) {
+  CompleteCryptoHandshake();
+  EXPECT_TRUE(stream_->encryption_established());
+  EXPECT_TRUE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, MessageAfterHandshake) {
+  CompleteCryptoHandshake();
+
+  EXPECT_CALL(*connection_, SendConnectionClose(
+      QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE));
+  message_.set_tag(kCHLO);
+  ConstructHandshakeMessage();
+  stream_->ProcessRawData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoClientStreamTest, BadMessageType) {
+  EXPECT_TRUE(stream_->CryptoConnect());
+
+  message_.set_tag(kCHLO);
+  ConstructHandshakeMessage();
+
+  EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(
+        QUIC_INVALID_CRYPTO_MESSAGE_TYPE, "Expected REJ"));
+  stream_->ProcessRawData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoClientStreamTest, NegotiatedParameters) {
+  CompleteCryptoHandshake();
+
+  const QuicConfig* config = session_->config();
+  EXPECT_EQ(kQBIC, config->CongestionFeedback());
+  EXPECT_EQ(kMaximumIdleTimeoutSecs,
+            config->IdleConnectionStateLifetime().ToSeconds());
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection,
+            config->MaxStreamsPerConnection());
+  EXPECT_EQ(0, config->KeepaliveTimeout().ToSeconds());
+
+  const QuicCryptoNegotiatedParameters& crypto_params(
+      stream_->crypto_negotiated_params());
+  EXPECT_EQ(crypto_config_.aead[0], crypto_params.aead);
+  EXPECT_EQ(crypto_config_.kexs[0], crypto_params.key_exchange);
+}
+
+TEST_F(QuicCryptoClientStreamTest, InvalidHostname) {
+  QuicServerId server_id("invalid", 80, false, PRIVACY_MODE_DISABLED);
+  stream_.reset(new QuicCryptoClientStream(server_id, session_.get(), nullptr,
+                                           &crypto_config_));
+  session_->SetCryptoStream(stream_.get());
+
+  CompleteCryptoHandshake();
+  EXPECT_TRUE(stream_->encryption_established());
+  EXPECT_TRUE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, ExpiredServerConfig) {
+  // Seed the config with a cached server config.
+  CompleteCryptoHandshake();
+
+  connection_ = new PacketSavingConnection(true);
+  session_.reset(new TestClientSession(connection_, DefaultQuicConfig()));
+  stream_.reset(new QuicCryptoClientStream(server_id_, session_.get(), nullptr,
+                                           &crypto_config_));
+
+  session_->SetCryptoStream(stream_.get());
+  session_->config()->SetDefaults();
+
+  // Advance time 5 years to ensure that we pass the expiry time of the cached
+  // server config.
+  reinterpret_cast<MockClock*>(const_cast<QuicClock*>(connection_->clock()))
+      ->AdvanceTime(QuicTime::Delta::FromSeconds(60 * 60 * 24 * 365 * 5));
+
+  // Check that a client hello was sent and that CryptoConnect doesn't fail
+  // with an error.
+  EXPECT_TRUE(stream_->CryptoConnect());
+  ASSERT_EQ(1u, connection_->packets_.size());
+}
+
+TEST_F(QuicCryptoClientStreamTest, ServerConfigUpdate) {
+  // Test that the crypto client stream can receive server config updates after
+  // the connection has been established.
+  CompleteCryptoHandshake();
+
+  QuicCryptoClientConfig::CachedState* state =
+      crypto_config_.LookupOrCreate(server_id_);
+
+  // Ensure cached STK is different to what we send in the handshake.
+  EXPECT_NE("xstk", state->source_address_token());
+
+  // Initialize using {...} syntax to avoid trailing \0 if converting from
+  // string.
+  unsigned char stk[] = { 'x', 's', 't', 'k' };
+
+  // Minimum SCFG that passes config validation checks.
+  unsigned char scfg[] = {
+    // SCFG
+    0x53, 0x43, 0x46, 0x47,
+    // num entries
+    0x01, 0x00,
+    // padding
+    0x00, 0x00,
+    // EXPY
+    0x45, 0x58, 0x50, 0x59,
+    // EXPY end offset
+    0x08, 0x00, 0x00, 0x00,
+    // Value
+    '1',  '2',  '3',  '4',
+    '5',  '6',  '7',  '8'
+  };
+
+  CryptoHandshakeMessage server_config_update;
+  server_config_update.set_tag(kSCUP);
+  server_config_update.SetValue(kSourceAddressTokenTag, stk);
+  server_config_update.SetValue(kSCFG, scfg);
+
+  scoped_ptr<QuicData> data(
+      CryptoFramer::ConstructHandshakeMessage(server_config_update));
+  stream_->ProcessRawData(data->data(), data->length());
+
+  // Make sure that the STK and SCFG are cached correctly.
+  EXPECT_EQ("xstk", state->source_address_token());
+
+  string cached_scfg = state->server_config();
+  test::CompareCharArraysWithHexError(
+      "scfg", cached_scfg.data(), cached_scfg.length(),
+      QuicUtils::AsChars(scfg), arraysize(scfg));
+}
+
+TEST_F(QuicCryptoClientStreamTest, ServerConfigUpdateBeforeHandshake) {
+  EXPECT_CALL(*connection_, SendConnectionClose(
+      QUIC_CRYPTO_UPDATE_BEFORE_HANDSHAKE_COMPLETE));
+  CryptoHandshakeMessage server_config_update;
+  server_config_update.set_tag(kSCUP);
+  scoped_ptr<QuicData> data(
+      CryptoFramer::ConstructHandshakeMessage(server_config_update));
+  stream_->ProcessRawData(data->data(), data->length());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_crypto_server_stream.cc b/net/quic/quic_crypto_server_stream.cc
new file mode 100644
index 0000000..eebab90
--- /dev/null
+++ b/net/quic/quic_crypto_server_stream.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_server_stream.h"
+
+#include "base/base64.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/crypto/source_address_token.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+void ServerHelloNotifier::OnAckNotification(
+    int num_original_packets,
+    int num_original_bytes,
+    int num_retransmitted_packets,
+    int num_retransmitted_bytes,
+    QuicTime::Delta delta_largest_observed) {
+  server_stream_->OnServerHelloAcked();
+}
+
+QuicCryptoServerStream::QuicCryptoServerStream(
+    const QuicCryptoServerConfig& crypto_config,
+    QuicSession* session)
+    : QuicCryptoStream(session),
+      crypto_config_(crypto_config),
+      validate_client_hello_cb_(nullptr),
+      num_handshake_messages_(0),
+      num_server_config_update_messages_sent_(0) {}
+
+QuicCryptoServerStream::~QuicCryptoServerStream() {
+  CancelOutstandingCallbacks();
+}
+
+void QuicCryptoServerStream::CancelOutstandingCallbacks() {
+  // Detach from the validation callback.  Calling this multiple times is safe.
+  if (validate_client_hello_cb_ != nullptr) {
+    validate_client_hello_cb_->Cancel();
+  }
+}
+
+void QuicCryptoServerStream::OnHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  QuicCryptoStream::OnHandshakeMessage(message);
+  ++num_handshake_messages_;
+
+  // Do not process handshake messages after the handshake is confirmed.
+  if (handshake_confirmed_) {
+    CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+    return;
+  }
+
+  if (message.tag() != kCHLO) {
+    CloseConnection(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+    return;
+  }
+
+  if (validate_client_hello_cb_ != nullptr) {
+    // Already processing some other handshake message.  The protocol
+    // does not allow for clients to send multiple handshake messages
+    // before the server has a chance to respond.
+    CloseConnection(QUIC_CRYPTO_MESSAGE_WHILE_VALIDATING_CLIENT_HELLO);
+    return;
+  }
+
+  validate_client_hello_cb_ = new ValidateCallback(this);
+  return crypto_config_.ValidateClientHello(
+      message,
+      session()->connection()->peer_address(),
+      session()->connection()->clock(),
+      validate_client_hello_cb_);
+}
+
+void QuicCryptoServerStream::FinishProcessingHandshakeMessage(
+    const CryptoHandshakeMessage& message,
+    const ValidateClientHelloResultCallback::Result& result) {
+  // Clear the callback that got us here.
+  DCHECK(validate_client_hello_cb_ != nullptr);
+  validate_client_hello_cb_ = nullptr;
+
+  string error_details;
+  CryptoHandshakeMessage reply;
+  QuicErrorCode error = ProcessClientHello(
+      message, result, &reply, &error_details);
+
+  if (error != QUIC_NO_ERROR) {
+    CloseConnectionWithDetails(error, error_details);
+    return;
+  }
+
+  if (reply.tag() != kSHLO) {
+    SendHandshakeMessage(reply);
+    return;
+  }
+
+  // If we are returning a SHLO then we accepted the handshake.
+  QuicConfig* config = session()->config();
+  OverrideQuicConfigDefaults(config);
+  error = config->ProcessPeerHello(message, CLIENT, &error_details);
+  if (error != QUIC_NO_ERROR) {
+    CloseConnectionWithDetails(error, error_details);
+    return;
+  }
+  session()->OnConfigNegotiated();
+
+  config->ToHandshakeMessage(&reply);
+
+  // Receiving a full CHLO implies the client is prepared to decrypt with
+  // the new server write key.  We can start to encrypt with the new server
+  // write key.
+  //
+  // NOTE: the SHLO will be encrypted with the new server write key.
+  session()->connection()->SetEncrypter(
+      ENCRYPTION_INITIAL,
+      crypto_negotiated_params_.initial_crypters.encrypter.release());
+  session()->connection()->SetDefaultEncryptionLevel(
+      ENCRYPTION_INITIAL);
+  // Set the decrypter immediately so that we no longer accept unencrypted
+  // packets.
+  session()->connection()->SetDecrypter(
+      crypto_negotiated_params_.initial_crypters.decrypter.release(),
+      ENCRYPTION_INITIAL);
+
+  // We want to be notified when the SHLO is ACKed so that we can disable
+  // HANDSHAKE_MODE in the sent packet manager.
+  if (session()->connection()->version() <= QUIC_VERSION_21) {
+    SendHandshakeMessage(reply);
+  } else {
+    scoped_refptr<ServerHelloNotifier> server_hello_notifier(
+        new ServerHelloNotifier(this));
+    SendHandshakeMessage(reply, server_hello_notifier.get());
+  }
+
+  session()->connection()->SetEncrypter(
+      ENCRYPTION_FORWARD_SECURE,
+      crypto_negotiated_params_.forward_secure_crypters.encrypter.release());
+  session()->connection()->SetDefaultEncryptionLevel(
+      ENCRYPTION_FORWARD_SECURE);
+  session()->connection()->SetAlternativeDecrypter(
+      crypto_negotiated_params_.forward_secure_crypters.decrypter.release(),
+      ENCRYPTION_FORWARD_SECURE, false /* don't latch */);
+
+  encryption_established_ = true;
+  handshake_confirmed_ = true;
+  session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+
+  // Now that the handshake is complete, send an updated server config and
+  // source-address token to the client.
+  SendServerConfigUpdate(nullptr);
+}
+
+void QuicCryptoServerStream::SendServerConfigUpdate(
+    const CachedNetworkParameters* cached_network_params) {
+  if (session()->connection()->version() <= QUIC_VERSION_21 ||
+      !handshake_confirmed_) {
+    return;
+  }
+
+  CryptoHandshakeMessage server_config_update_message;
+  if (!crypto_config_.BuildServerConfigUpdateMessage(
+          session()->connection()->peer_address(),
+          session()->connection()->clock(),
+          session()->connection()->random_generator(),
+          crypto_negotiated_params_,
+          cached_network_params,
+          &server_config_update_message)) {
+    DVLOG(1) << "Server: Failed to build server config update (SCUP)!";
+    return;
+  }
+
+  DVLOG(1) << "Server: Sending server config update: "
+           << server_config_update_message.DebugString();
+  const QuicData& data = server_config_update_message.GetSerialized();
+  WriteOrBufferData(string(data.data(), data.length()), false, nullptr);
+
+  ++num_server_config_update_messages_sent_;
+}
+
+void QuicCryptoServerStream::OnServerHelloAcked() {
+  session()->connection()->OnHandshakeComplete();
+}
+
+bool QuicCryptoServerStream::GetBase64SHA256ClientChannelID(
+    string* output) const {
+  if (!encryption_established_ ||
+      crypto_negotiated_params_.channel_id.empty()) {
+    return false;
+  }
+
+  const string& channel_id(crypto_negotiated_params_.channel_id);
+  scoped_ptr<crypto::SecureHash> hash(
+      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+  hash->Update(channel_id.data(), channel_id.size());
+  uint8 digest[32];
+  hash->Finish(digest, sizeof(digest));
+
+  base::Base64Encode(string(
+      reinterpret_cast<const char*>(digest), sizeof(digest)), output);
+  // Remove padding.
+  size_t len = output->size();
+  if (len >= 2) {
+    if ((*output)[len - 1] == '=') {
+      len--;
+      if ((*output)[len - 1] == '=') {
+        len--;
+      }
+      output->resize(len);
+    }
+  }
+  return true;
+}
+
+QuicErrorCode QuicCryptoServerStream::ProcessClientHello(
+    const CryptoHandshakeMessage& message,
+    const ValidateClientHelloResultCallback::Result& result,
+    CryptoHandshakeMessage* reply,
+    string* error_details) {
+  return crypto_config_.ProcessClientHello(
+      result,
+      session()->connection()->connection_id(),
+      session()->connection()->peer_address(),
+      session()->connection()->version(),
+      session()->connection()->supported_versions(),
+      session()->connection()->clock(),
+      session()->connection()->random_generator(),
+      &crypto_negotiated_params_, reply, error_details);
+}
+
+void QuicCryptoServerStream::OverrideQuicConfigDefaults(QuicConfig* config) {
+}
+
+QuicCryptoServerStream::ValidateCallback::ValidateCallback(
+    QuicCryptoServerStream* parent) : parent_(parent) {
+}
+
+void QuicCryptoServerStream::ValidateCallback::Cancel() { parent_ = nullptr; }
+
+void QuicCryptoServerStream::ValidateCallback::RunImpl(
+    const CryptoHandshakeMessage& client_hello,
+    const Result& result) {
+  if (parent_ != nullptr) {
+    parent_->FinishProcessingHandshakeMessage(client_hello, result);
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_crypto_server_stream.h b/net/quic/quic_crypto_server_stream.h
new file mode 100644
index 0000000..46ac4f3
--- /dev/null
+++ b/net/quic/quic_crypto_server_stream.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
+
+#include <string>
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+
+namespace net {
+
+class CachedNetworkParameters;
+class CryptoHandshakeMessage;
+class QuicCryptoServerConfig;
+class QuicCryptoServerStream;
+class QuicSession;
+
+namespace test {
+class CryptoTestUtils;
+}  // namespace test
+
+// Receives a notification when the server hello (SHLO) has been ACKed by the
+// peer. At this point we disable HANDSHAKE_MODE in the sent packet manager.
+class NET_EXPORT_PRIVATE ServerHelloNotifier : public
+    QuicAckNotifier::DelegateInterface {
+ public:
+  explicit ServerHelloNotifier(QuicCryptoServerStream* stream)
+      : server_stream_(stream) {}
+
+  // QuicAckNotifier::DelegateInterface implementation
+  virtual void OnAckNotification(
+      int num_original_packets,
+      int num_original_bytes,
+      int num_retransmitted_packets,
+      int num_retransmitted_bytes,
+      QuicTime::Delta delta_largest_observed) OVERRIDE;
+
+ private:
+  virtual ~ServerHelloNotifier() {}
+
+  QuicCryptoServerStream* server_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(ServerHelloNotifier);
+};
+
+class NET_EXPORT_PRIVATE QuicCryptoServerStream : public QuicCryptoStream {
+ public:
+  QuicCryptoServerStream(const QuicCryptoServerConfig& crypto_config,
+                         QuicSession* session);
+  virtual ~QuicCryptoServerStream();
+
+  // Cancel any outstanding callbacks, such as asynchronous validation of client
+  // hello.
+  void CancelOutstandingCallbacks();
+
+  // CryptoFramerVisitorInterface implementation
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+
+  // GetBase64SHA256ClientChannelID sets |*output| to the base64 encoded,
+  // SHA-256 hash of the client's ChannelID key and returns true, if the client
+  // presented a ChannelID. Otherwise it returns false.
+  bool GetBase64SHA256ClientChannelID(std::string* output) const;
+
+  uint8 num_handshake_messages() const { return num_handshake_messages_; }
+
+  int num_server_config_update_messages_sent() const {
+    return num_server_config_update_messages_sent_;
+  }
+
+  // Sends the latest server config and source-address token to the client.
+  virtual void SendServerConfigUpdate(
+      const CachedNetworkParameters* cached_network_params);
+
+  // Called by the ServerHello AckNotifier once the SHLO has been ACKed by the
+  // client.
+  void OnServerHelloAcked();
+
+ protected:
+  virtual QuicErrorCode ProcessClientHello(
+      const CryptoHandshakeMessage& message,
+      const ValidateClientHelloResultCallback::Result& result,
+      CryptoHandshakeMessage* reply,
+      std::string* error_details);
+
+  // Hook that allows the server to set QuicConfig defaults just
+  // before going through the parameter negotiation step.
+  virtual void OverrideQuicConfigDefaults(QuicConfig* config);
+
+ private:
+  friend class test::CryptoTestUtils;
+
+  class ValidateCallback : public ValidateClientHelloResultCallback {
+   public:
+    explicit ValidateCallback(QuicCryptoServerStream* parent);
+    // To allow the parent to detach itself from the callback before deletion.
+    void Cancel();
+
+    // From ValidateClientHelloResultCallback
+    virtual void RunImpl(const CryptoHandshakeMessage& client_hello,
+                         const Result& result) OVERRIDE;
+
+   private:
+    QuicCryptoServerStream* parent_;
+
+    DISALLOW_COPY_AND_ASSIGN(ValidateCallback);
+  };
+
+  // Invoked by ValidateCallback::RunImpl once initial validation of
+  // the client hello is complete.  Finishes processing of the client
+  // hello message and handles handshake success/failure.
+  void FinishProcessingHandshakeMessage(
+      const CryptoHandshakeMessage& message,
+      const ValidateClientHelloResultCallback::Result& result);
+
+  // crypto_config_ contains crypto parameters for the handshake.
+  const QuicCryptoServerConfig& crypto_config_;
+
+  // Pointer to the active callback that will receive the result of
+  // the client hello validation request and forward it to
+  // FinishProcessingHandshakeMessage for processing.  nullptr if no
+  // handshake message is being validated.
+  ValidateCallback* validate_client_hello_cb_;
+
+  // Number of handshake messages received by this stream.
+  uint8 num_handshake_messages_;
+
+  // Number of server config update (SCUP) messages sent by this stream.
+  int num_server_config_update_messages_sent_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoServerStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
diff --git a/net/quic/quic_crypto_server_stream_test.cc b/net/quic/quic_crypto_server_stream_test.cc
new file mode 100644
index 0000000..6ce059b
--- /dev/null
+++ b/net/quic/quic_crypto_server_stream_test.cc
@@ -0,0 +1,286 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_server_stream.h"
+
+#include <map>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/delayed_verify_strike_register_client.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+class QuicConnection;
+class ReliableQuicStream;
+}  // namespace net
+
+using std::pair;
+using testing::_;
+
+namespace net {
+namespace test {
+
+class QuicCryptoServerConfigPeer {
+ public:
+  static string GetPrimaryOrbit(const QuicCryptoServerConfig& config) {
+    base::AutoLock lock(config.configs_lock_);
+    CHECK(config.primary_config_.get() != nullptr);
+    return string(reinterpret_cast<const char*>(config.primary_config_->orbit),
+                  kOrbitSize);
+  }
+};
+
+namespace {
+
+const char kServerHostname[] = "test.example.com";
+const uint16 kServerPort = 80;
+
+class QuicCryptoServerStreamTest : public ::testing::TestWithParam<bool> {
+ public:
+  QuicCryptoServerStreamTest()
+      : connection_(new PacketSavingConnection(true)),
+        session_(connection_, DefaultQuicConfig()),
+        crypto_config_(QuicCryptoServerConfig::TESTING,
+                       QuicRandom::GetInstance()),
+        stream_(crypto_config_, &session_),
+        strike_register_client_(nullptr) {
+    config_.SetDefaults();
+    session_.config()->SetDefaults();
+    session_.SetCryptoStream(&stream_);
+    // We advance the clock initially because the default time is zero and the
+    // strike register worries that we've just overflowed a uint32 time.
+    connection_->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+    // TODO(wtc): replace this with ProofSourceForTesting() when Chromium has
+    // a working ProofSourceForTesting().
+    crypto_config_.SetProofSource(CryptoTestUtils::FakeProofSourceForTesting());
+    crypto_config_.set_strike_register_no_startup_period();
+
+    CryptoTestUtils::SetupCryptoServerConfigForTest(
+        connection_->clock(), connection_->random_generator(),
+        session_.config(), &crypto_config_);
+
+    if (AsyncStrikeRegisterVerification()) {
+      string orbit =
+          QuicCryptoServerConfigPeer::GetPrimaryOrbit(crypto_config_);
+      strike_register_client_ = new DelayedVerifyStrikeRegisterClient(
+          10000,  // strike_register_max_entries
+          static_cast<uint32>(connection_->clock()->WallNow().ToUNIXSeconds()),
+          60,  // strike_register_window_secs
+          reinterpret_cast<const uint8 *>(orbit.data()),
+          StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
+      strike_register_client_->StartDelayingVerification();
+      crypto_config_.SetStrikeRegisterClient(strike_register_client_);
+    }
+  }
+
+  bool AsyncStrikeRegisterVerification() {
+    return GetParam();
+  }
+
+  void ConstructHandshakeMessage() {
+    CryptoFramer framer;
+    message_data_.reset(framer.ConstructHandshakeMessage(message_));
+  }
+
+  int CompleteCryptoHandshake() {
+    return CryptoTestUtils::HandshakeWithFakeClient(connection_, &stream_,
+                                                    client_options_);
+  }
+
+ protected:
+  PacketSavingConnection* connection_;
+  TestClientSession session_;
+  QuicConfig config_;
+  QuicCryptoServerConfig crypto_config_;
+  QuicCryptoServerStream stream_;
+  CryptoHandshakeMessage message_;
+  scoped_ptr<QuicData> message_data_;
+  CryptoTestUtils::FakeClientOptions client_options_;
+  DelayedVerifyStrikeRegisterClient* strike_register_client_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicCryptoServerStreamTest, testing::Bool());
+
+TEST_P(QuicCryptoServerStreamTest, NotInitiallyConected) {
+  EXPECT_FALSE(stream_.encryption_established());
+  EXPECT_FALSE(stream_.handshake_confirmed());
+}
+
+TEST_P(QuicCryptoServerStreamTest, ConnectedAfterCHLO) {
+  // CompleteCryptoHandshake returns the number of client hellos sent. This
+  // test should send:
+  //   * One to get a source-address token and certificates.
+  //   * One to complete the handshake.
+  EXPECT_EQ(2, CompleteCryptoHandshake());
+  EXPECT_TRUE(stream_.encryption_established());
+  EXPECT_TRUE(stream_.handshake_confirmed());
+  EXPECT_EQ(1, stream_.num_server_config_update_messages_sent());
+}
+
+TEST_P(QuicCryptoServerStreamTest, ZeroRTT) {
+  PacketSavingConnection* client_conn = new PacketSavingConnection(false);
+  PacketSavingConnection* server_conn = new PacketSavingConnection(false);
+  client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+  server_conn->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+
+  QuicConfig client_config;
+  client_config.SetDefaults();
+  scoped_ptr<TestClientSession> client_session(
+      new TestClientSession(client_conn, client_config));
+  QuicCryptoClientConfig client_crypto_config;
+  client_crypto_config.SetDefaults();
+
+  QuicServerId server_id(kServerHostname, kServerPort, false,
+                         PRIVACY_MODE_DISABLED);
+  scoped_ptr<QuicCryptoClientStream> client(new QuicCryptoClientStream(
+      server_id, client_session.get(), nullptr, &client_crypto_config));
+  client_session->SetCryptoStream(client.get());
+
+  // Do a first handshake in order to prime the client config with the server's
+  // information.
+  CHECK(client->CryptoConnect());
+  CHECK_EQ(1u, client_conn->packets_.size());
+
+  scoped_ptr<TestSession> server_session(new TestSession(server_conn, config_));
+  scoped_ptr<QuicCryptoServerStream> server(
+      new QuicCryptoServerStream(crypto_config_, server_session.get()));
+  server_session->SetCryptoStream(server.get());
+
+  CryptoTestUtils::CommunicateHandshakeMessages(
+      client_conn, client.get(), server_conn, server.get());
+  EXPECT_EQ(2, client->num_sent_client_hellos());
+
+  // Now do another handshake, hopefully in 0-RTT.
+  LOG(INFO) << "Resetting for 0-RTT handshake attempt";
+
+  client_conn = new PacketSavingConnection(false);
+  server_conn = new PacketSavingConnection(false);
+  // We need to advance time past the strike-server window so that it's
+  // authoritative in this time span.
+  client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(102000));
+  server_conn->AdvanceTime(QuicTime::Delta::FromSeconds(102000));
+
+  // This causes the client's nonce to be different and thus stops the
+  // strike-register from rejecting the repeated nonce.
+  reinterpret_cast<MockRandom*>(client_conn->random_generator())->ChangeValue();
+  client_session.reset(new TestClientSession(client_conn, client_config));
+  server_session.reset(new TestSession(server_conn, config_));
+  client.reset(new QuicCryptoClientStream(server_id, client_session.get(),
+                                          nullptr, &client_crypto_config));
+  client_session->SetCryptoStream(client.get());
+
+  server.reset(new QuicCryptoServerStream(crypto_config_,
+                                          server_session.get()));
+  server_session->SetCryptoStream(server.get());
+
+  CHECK(client->CryptoConnect());
+
+  if (AsyncStrikeRegisterVerification()) {
+    EXPECT_FALSE(client->handshake_confirmed());
+    EXPECT_FALSE(server->handshake_confirmed());
+
+    // Advance the handshake.  Expect that the server will be stuck
+    // waiting for client nonce verification to complete.
+    pair<size_t, size_t> messages_moved = CryptoTestUtils::AdvanceHandshake(
+        client_conn, client.get(), 0, server_conn, server.get(), 0);
+    EXPECT_EQ(1u, messages_moved.first);
+    EXPECT_EQ(0u, messages_moved.second);
+    EXPECT_EQ(1, strike_register_client_->PendingVerifications());
+    EXPECT_FALSE(client->handshake_confirmed());
+    EXPECT_FALSE(server->handshake_confirmed());
+
+    // The server handshake completes once the nonce verification completes.
+    strike_register_client_->RunPendingVerifications();
+    EXPECT_FALSE(client->handshake_confirmed());
+    EXPECT_TRUE(server->handshake_confirmed());
+
+    messages_moved = CryptoTestUtils::AdvanceHandshake(
+        client_conn, client.get(), messages_moved.first,
+        server_conn, server.get(), messages_moved.second);
+    EXPECT_EQ(1u, messages_moved.first);
+    EXPECT_EQ(1u, messages_moved.second);
+    EXPECT_TRUE(client->handshake_confirmed());
+    EXPECT_TRUE(server->handshake_confirmed());
+  } else {
+    CryptoTestUtils::CommunicateHandshakeMessages(
+        client_conn, client.get(), server_conn, server.get());
+  }
+
+  EXPECT_EQ(1, client->num_sent_client_hellos());
+  EXPECT_EQ(1, server->num_server_config_update_messages_sent());
+}
+
+TEST_P(QuicCryptoServerStreamTest, MessageAfterHandshake) {
+  CompleteCryptoHandshake();
+  EXPECT_CALL(*connection_, SendConnectionClose(
+      QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE));
+  message_.set_tag(kCHLO);
+  ConstructHandshakeMessage();
+  stream_.ProcessRawData(message_data_->data(), message_data_->length());
+}
+
+TEST_P(QuicCryptoServerStreamTest, BadMessageType) {
+  message_.set_tag(kSHLO);
+  ConstructHandshakeMessage();
+  EXPECT_CALL(*connection_, SendConnectionClose(
+      QUIC_INVALID_CRYPTO_MESSAGE_TYPE));
+  stream_.ProcessRawData(message_data_->data(), message_data_->length());
+}
+
+TEST_P(QuicCryptoServerStreamTest, WithoutCertificates) {
+  crypto_config_.SetProofSource(nullptr);
+  client_options_.dont_verify_certs = true;
+
+  // Only 2 client hellos need to be sent in the no-certs case: one to get the
+  // source-address token and the second to finish.
+  EXPECT_EQ(2, CompleteCryptoHandshake());
+  EXPECT_TRUE(stream_.encryption_established());
+  EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+TEST_P(QuicCryptoServerStreamTest, ChannelID) {
+  client_options_.channel_id_enabled = true;
+  client_options_.channel_id_source_async = false;
+  // CompleteCryptoHandshake verifies
+  // stream_.crypto_negotiated_params().channel_id is correct.
+  EXPECT_EQ(2, CompleteCryptoHandshake());
+  EXPECT_TRUE(stream_.encryption_established());
+  EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+TEST_P(QuicCryptoServerStreamTest, ChannelIDAsync) {
+  client_options_.channel_id_enabled = true;
+  client_options_.channel_id_source_async = true;
+  // CompleteCryptoHandshake verifies
+  // stream_.crypto_negotiated_params().channel_id is correct.
+  EXPECT_EQ(2, CompleteCryptoHandshake());
+  EXPECT_TRUE(stream_.encryption_established());
+  EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+TEST_P(QuicCryptoServerStreamTest, OnlySendSCUPAfterHandshakeComplete) {
+  // An attempt to send a SCUP before completing handshake should fail.
+  stream_.SendServerConfigUpdate(nullptr);
+  EXPECT_EQ(0, stream_.num_server_config_update_messages_sent());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_crypto_stream.cc b/net/quic/quic_crypto_stream.cc
new file mode 100644
index 0000000..bef06aa
--- /dev/null
+++ b/net/quic/quic_crypto_stream.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_stream.h"
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_utils.h"
+
+using std::string;
+using base::StringPiece;
+
+namespace net {
+
+#define ENDPOINT (session()->is_server() ? "Server: " : " Client: ")
+
+QuicCryptoStream::QuicCryptoStream(QuicSession* session)
+    : ReliableQuicStream(kCryptoStreamId, session),
+      encryption_established_(false),
+      handshake_confirmed_(false) {
+  crypto_framer_.set_visitor(this);
+  if (version() < QUIC_VERSION_21) {
+    // Prior to QUIC_VERSION_21 the crypto stream is not subject to any flow
+    // control.
+    DisableFlowControl();
+  }
+  // The crypto stream is exempt from connection level flow control.
+  DisableConnectionFlowControlForThisStream();
+}
+
+void QuicCryptoStream::OnError(CryptoFramer* framer) {
+  DLOG(WARNING) << "Error processing crypto data: "
+                << QuicUtils::ErrorToString(framer->error());
+}
+
+void QuicCryptoStream::OnHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  DVLOG(1) << ENDPOINT << "Received " << message.DebugString();
+  session()->OnCryptoHandshakeMessageReceived(message);
+}
+
+uint32 QuicCryptoStream::ProcessRawData(const char* data,
+                                        uint32 data_len) {
+  if (!crypto_framer_.ProcessInput(StringPiece(data, data_len))) {
+    CloseConnection(crypto_framer_.error());
+    return 0;
+  }
+  return data_len;
+}
+
+QuicPriority QuicCryptoStream::EffectivePriority() const {
+  return QuicUtils::HighestPriority();
+}
+
+void QuicCryptoStream::SendHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  SendHandshakeMessage(message, nullptr);
+}
+
+void QuicCryptoStream::SendHandshakeMessage(
+    const CryptoHandshakeMessage& message,
+    QuicAckNotifier::DelegateInterface* delegate) {
+  DVLOG(1) << ENDPOINT << "Sending " << message.DebugString();
+  session()->OnCryptoHandshakeMessageSent(message);
+  const QuicData& data = message.GetSerialized();
+  // TODO(wtc): check the return value.
+  WriteOrBufferData(string(data.data(), data.length()), false, delegate);
+}
+
+bool QuicCryptoStream::ExportKeyingMaterial(
+    StringPiece label,
+    StringPiece context,
+    size_t result_len,
+    string* result) const {
+  if (!handshake_confirmed()) {
+    DLOG(ERROR) << "ExportKeyingMaterial was called before forward-secure"
+                << "encryption was established.";
+    return false;
+  }
+  return CryptoUtils::ExportKeyingMaterial(
+      crypto_negotiated_params_.subkey_secret,
+      label,
+      context,
+      result_len,
+      result);
+}
+
+const QuicCryptoNegotiatedParameters&
+QuicCryptoStream::crypto_negotiated_params() const {
+  return crypto_negotiated_params_;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_crypto_stream.h b/net/quic/quic_crypto_stream.h
new file mode 100644
index 0000000..dc8b227
--- /dev/null
+++ b/net/quic/quic_crypto_stream.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_STREAM_H_
+
+#include "base/basictypes.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+class QuicSession;
+
+// Crypto handshake messages in QUIC take place over a reserved
+// reliable stream with the id 1.  Each endpoint (client and server)
+// will allocate an instance of a subclass of QuicCryptoStream
+// to send and receive handshake messages.  (In the normal 1-RTT
+// handshake, the client will send a client hello, CHLO, message.
+// The server will receive this message and respond with a server
+// hello message, SHLO.  At this point both sides will have established
+// a crypto context they can use to send encrypted messages.
+//
+// For more details: http://goto.google.com/quic-crypto
+class NET_EXPORT_PRIVATE QuicCryptoStream
+    : public ReliableQuicStream,
+      public CryptoFramerVisitorInterface {
+ public:
+  explicit QuicCryptoStream(QuicSession* session);
+
+  // CryptoFramerVisitorInterface implementation
+  virtual void OnError(CryptoFramer* framer) OVERRIDE;
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+
+  // ReliableQuicStream implementation
+  virtual uint32 ProcessRawData(const char* data, uint32 data_len) OVERRIDE;
+  virtual QuicPriority EffectivePriority() const OVERRIDE;
+
+  // Sends |message| to the peer.
+  // TODO(wtc): return a success/failure status.
+  void SendHandshakeMessage(const CryptoHandshakeMessage& message);
+  // As above, but registers |delegate| for notification when |message| has been
+  // ACKed by the peer.
+  void SendHandshakeMessage(const CryptoHandshakeMessage& message,
+                            QuicAckNotifier::DelegateInterface* delegate);
+
+  // Performs key extraction to derive a new secret of |result_len| bytes
+  // dependent on |label|, |context|, and the stream's negotiated subkey secret.
+  // Returns false if the handshake has not been confirmed or the parameters are
+  // invalid (e.g. |label| contains null bytes); returns true on success.
+  bool ExportKeyingMaterial(base::StringPiece label,
+                            base::StringPiece context,
+                            size_t result_len,
+                            std::string* result) const;
+
+  bool encryption_established() const { return encryption_established_; }
+  bool handshake_confirmed() const { return handshake_confirmed_; }
+
+  const QuicCryptoNegotiatedParameters& crypto_negotiated_params() const;
+
+ protected:
+  bool encryption_established_;
+  bool handshake_confirmed_;
+
+  QuicCryptoNegotiatedParameters crypto_negotiated_params_;
+
+ private:
+  CryptoFramer crypto_framer_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_CRYPTO_STREAM_H_
diff --git a/net/quic/quic_crypto_stream_test.cc b/net/quic/quic_crypto_stream_test.cc
new file mode 100644
index 0000000..4ea8f49
--- /dev/null
+++ b/net/quic/quic_crypto_stream_test.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_crypto_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockQuicCryptoStream : public QuicCryptoStream {
+ public:
+  explicit MockQuicCryptoStream(QuicSession* session)
+      : QuicCryptoStream(session) {
+  }
+
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE {
+    messages_.push_back(message);
+  }
+
+  vector<CryptoHandshakeMessage>* messages() {
+    return &messages_;
+  }
+
+ private:
+  vector<CryptoHandshakeMessage> messages_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockQuicCryptoStream);
+};
+
+class QuicCryptoStreamTest : public ::testing::Test {
+ public:
+  QuicCryptoStreamTest()
+      : connection_(new MockConnection(false)),
+        session_(connection_),
+        stream_(&session_) {
+    message_.set_tag(kSHLO);
+    message_.SetStringPiece(1, "abc");
+    message_.SetStringPiece(2, "def");
+    ConstructHandshakeMessage();
+  }
+
+  void ConstructHandshakeMessage() {
+    CryptoFramer framer;
+    message_data_.reset(framer.ConstructHandshakeMessage(message_));
+  }
+
+ protected:
+  MockConnection* connection_;
+  MockSession session_;
+  MockQuicCryptoStream stream_;
+  CryptoHandshakeMessage message_;
+  scoped_ptr<QuicData> message_data_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicCryptoStreamTest);
+};
+
+TEST_F(QuicCryptoStreamTest, NotInitiallyConected) {
+  EXPECT_FALSE(stream_.encryption_established());
+  EXPECT_FALSE(stream_.handshake_confirmed());
+}
+
+TEST_F(QuicCryptoStreamTest, ProcessRawData) {
+  EXPECT_EQ(message_data_->length(),
+            stream_.ProcessRawData(message_data_->data(),
+                                   message_data_->length()));
+  ASSERT_EQ(1u, stream_.messages()->size());
+  const CryptoHandshakeMessage& message = (*stream_.messages())[0];
+  EXPECT_EQ(kSHLO, message.tag());
+  EXPECT_EQ(2u, message.tag_value_map().size());
+  EXPECT_EQ("abc", CryptoTestUtils::GetValueForTag(message, 1));
+  EXPECT_EQ("def", CryptoTestUtils::GetValueForTag(message, 2));
+}
+
+TEST_F(QuicCryptoStreamTest, ProcessBadData) {
+  string bad(message_data_->data(), message_data_->length());
+  const int kFirstTagIndex = sizeof(uint32) +  // message tag
+                             sizeof(uint16) +  // number of tag-value pairs
+                             sizeof(uint16);   // padding
+  EXPECT_EQ(1, bad[kFirstTagIndex]);
+  bad[kFirstTagIndex] = 0x7F;  // out of order tag
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_CRYPTO_TAGS_OUT_OF_ORDER));
+  EXPECT_EQ(0u, stream_.ProcessRawData(bad.data(), bad.length()));
+}
+
+TEST_F(QuicCryptoStreamTest, NoConnectionLevelFlowControl) {
+  if (connection_->version() < QUIC_VERSION_21) {
+    EXPECT_FALSE(stream_.flow_controller()->IsEnabled());
+  } else {
+    EXPECT_TRUE(stream_.flow_controller()->IsEnabled());
+  }
+  EXPECT_FALSE(ReliableQuicStreamPeer::StreamContributesToConnectionFlowControl(
+      &stream_));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_data_reader.cc b/net/quic/quic_data_reader.cc
new file mode 100644
index 0000000..b9a6acf
--- /dev/null
+++ b/net/quic/quic_data_reader.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_data_reader.h"
+
+#include "net/base/int128.h"
+#include "net/quic/quic_protocol.h"
+
+using base::StringPiece;
+
+namespace net {
+
+QuicDataReader::QuicDataReader(const char* data, const size_t len)
+    : data_(data),
+      len_(len),
+      pos_(0) {
+}
+
+bool QuicDataReader::ReadUInt16(uint16* result) {
+  return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt32(uint32* result) {
+  return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt48(uint64* result) {
+  uint32 lo;
+  if (!ReadUInt32(&lo)) {
+    return false;
+  }
+
+  uint16 hi;
+  if (!ReadUInt16(&hi)) {
+    return false;
+  }
+
+  *result = hi;
+  *result <<= 32;
+  *result += lo;
+
+  return true;
+}
+
+bool QuicDataReader::ReadUInt64(uint64* result) {
+  return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt128(uint128* result) {
+  uint64 high_hash;
+  uint64 low_hash;
+
+  if (!ReadUInt64(&low_hash)) {
+    return false;
+  }
+  if (!ReadUInt64(&high_hash)) {
+    return false;
+  }
+
+  *result = uint128(high_hash, low_hash);
+  return true;
+}
+
+bool QuicDataReader::ReadUFloat16(uint64* result) {
+  uint16 value;
+  if (!ReadUInt16(&value)) {
+    return false;
+  }
+
+  *result = value;
+  if (*result < (1 << kUFloat16MantissaEffectiveBits)) {
+    // Fast path: either the value is denormalized (no hidden bit), or
+    // normalized (hidden bit set, exponent offset by one) with exponent zero.
+    // Zero exponent offset by one sets the bit exactly where the hidden bit is.
+    // So in both cases the value encodes itself.
+    return true;
+  }
+
+  uint16 exponent = value >> kUFloat16MantissaBits;  // No sign extend on uint!
+  // After the fast pass, the exponent is at least one (offset by one).
+  // Un-offset the exponent.
+  --exponent;
+  DCHECK_GE(exponent, 1);
+  DCHECK_LE(exponent, kUFloat16MaxExponent);
+  // Here we need to clear the exponent and set the hidden bit. We have already
+  // decremented the exponent, so when we subtract it, it leaves behind the
+  // hidden bit.
+  *result -= exponent << kUFloat16MantissaBits;
+  *result <<= exponent;
+  DCHECK_GE(value, 1 << kUFloat16MantissaEffectiveBits);
+  DCHECK_LE(value, kUFloat16MaxValue);
+  return true;
+}
+
+bool QuicDataReader::ReadStringPiece16(StringPiece* result) {
+  // Read resultant length.
+  uint16 result_len;
+  if (!ReadUInt16(&result_len)) {
+    // OnFailure() already called.
+    return false;
+  }
+
+  return ReadStringPiece(result, result_len);
+}
+
+bool QuicDataReader::ReadStringPiece(StringPiece* result, size_t size) {
+  // Make sure that we have enough data to read.
+  if (!CanRead(size)) {
+    OnFailure();
+    return false;
+  }
+
+  // Set result.
+  result->set(data_ + pos_, size);
+
+  // Iterate.
+  pos_ += size;
+
+  return true;
+}
+
+StringPiece QuicDataReader::ReadRemainingPayload() {
+  StringPiece payload = PeekRemainingPayload();
+  pos_ = len_;
+  return payload;
+}
+
+StringPiece QuicDataReader::PeekRemainingPayload() {
+  return StringPiece(data_ + pos_, len_ - pos_);
+}
+
+bool QuicDataReader::ReadBytes(void* result, size_t size) {
+  // Make sure that we have enough data to read.
+  if (!CanRead(size)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  memcpy(result, data_ + pos_, size);
+
+  // Iterate.
+  pos_ += size;
+
+  return true;
+}
+
+bool QuicDataReader::IsDoneReading() const {
+  return len_ == pos_;
+}
+
+size_t QuicDataReader::BytesRemaining() const {
+  return len_ - pos_;
+}
+
+bool QuicDataReader::CanRead(size_t bytes) const {
+  return bytes <= (len_ - pos_);
+}
+
+void QuicDataReader::OnFailure() {
+  // Set our iterator to the end of the buffer so that further reads fail
+  // immediately.
+  pos_ = len_;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_data_reader.h b/net/quic/quic_data_reader.h
new file mode 100644
index 0000000..1686de0
--- /dev/null
+++ b/net/quic/quic_data_reader.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_DATA_READER_H_
+#define NET_QUIC_QUIC_DATA_READER_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Used for reading QUIC data. Though there isn't really anything terribly
+// QUIC-specific here, it's a helper class that's useful when doing QUIC
+// framing.
+//
+// To use, simply construct a QuicDataReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the QuicDataReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class NET_EXPORT_PRIVATE QuicDataReader {
+ public:
+  // Caller must provide an underlying buffer to work on.
+  QuicDataReader(const char* data, const size_t len);
+
+  // Empty destructor.
+  ~QuicDataReader() {}
+
+  // Reads a 16-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt16(uint16* result);
+
+  // Reads a 32-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt32(uint32* result);
+
+  // Reads a 48-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt48(uint64* result);
+
+  // Reads a 64-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt64(uint64* result);
+
+  // Reads a 128-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt128(uint128* result);
+
+  // Reads a 16-bit unsigned float into the given output parameter.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadUFloat16(uint64* result);
+
+  // Reads a string prefixed with 16-bit length into the given output parameter.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece16(base::StringPiece* result);
+
+  // Reads a given number of bytes into the given buffer. The buffer
+  // must be of adequate size.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece(base::StringPiece* result, size_t len);
+
+  // Returns the remaining payload as a StringPiece.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterator.
+  base::StringPiece ReadRemainingPayload();
+
+  // Returns the remaining payload as a StringPiece.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // DOES NOT forward the internal iterator.
+  base::StringPiece PeekRemainingPayload();
+
+  // Reads a given number of bytes into the given buffer. The buffer
+  // must be of adequate size.
+  // Forwards the internal iterator on success.
+  // Returns true on success, false otherwise.
+  bool ReadBytes(void* result, size_t size);
+
+  // Returns true if the entirety of the underlying buffer has been read via
+  // Read*() calls.
+  bool IsDoneReading() const;
+
+  // Returns the number of bytes remaining to be read.
+  size_t BytesRemaining() const;
+
+ private:
+  // Returns true if the underlying buffer has enough room to read the given
+  // amount of bytes.
+  bool CanRead(size_t bytes) const;
+
+  // To be called when a read fails for any reason.
+  void OnFailure();
+
+  // The data buffer that we're reading from.
+  const char* data_;
+
+  // The length of the data buffer that we're reading from.
+  const size_t len_;
+
+  // The location of the next read from our data buffer.
+  size_t pos_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicDataReader);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_DATA_READER_H_
diff --git a/net/quic/quic_data_stream.cc b/net/quic/quic_data_stream.cc
new file mode 100644
index 0000000..aac58f9
--- /dev/null
+++ b/net/quic/quic_data_stream.cc
@@ -0,0 +1,187 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_data_stream.h"
+
+#include "base/logging.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/quic_write_blocked_list.h"
+
+using base::StringPiece;
+using std::min;
+
+namespace net {
+
+#define ENDPOINT (session()->is_server() ? "Server: " : " Client: ")
+
+namespace {
+
+// This is somewhat arbitrary.  It's possible, but unlikely, we will either fail
+// to set a priority client-side, or cancel a stream before stripping the
+// priority from the wire server-side.  In either case, start out with a
+// priority in the middle.
+QuicPriority kDefaultPriority = 3;
+
+}  // namespace
+
+QuicDataStream::QuicDataStream(QuicStreamId id,
+                               QuicSession* session)
+    : ReliableQuicStream(id, session),
+      visitor_(nullptr),
+      headers_decompressed_(false),
+      priority_(kDefaultPriority),
+      decompression_failed_(false),
+      priority_parsed_(false) {
+  DCHECK_NE(kCryptoStreamId, id);
+  // Don't receive any callbacks from the sequencer until headers
+  // are complete.
+  sequencer()->SetBlockedUntilFlush();
+}
+
+QuicDataStream::~QuicDataStream() {
+}
+
+size_t QuicDataStream::WriteHeaders(
+    const SpdyHeaderBlock& header_block,
+    bool fin,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  size_t bytes_written = session()->WriteHeaders(
+      id(), header_block, fin, ack_notifier_delegate);
+  if (fin) {
+    // TODO(rch): Add test to ensure fin_sent_ is set whenever a fin is sent.
+    set_fin_sent(true);
+    CloseWriteSide();
+  }
+  return bytes_written;
+}
+
+size_t QuicDataStream::Readv(const struct iovec* iov, size_t iov_len) {
+  if (FinishedReadingHeaders()) {
+    // If the headers have been read, simply delegate to the sequencer's
+    // Readv method.
+    return sequencer()->Readv(iov, iov_len);
+  }
+  // Otherwise, copy decompressed header data into |iov|.
+  size_t bytes_consumed = 0;
+  size_t iov_index = 0;
+  while (iov_index < iov_len &&
+         decompressed_headers_.length() > bytes_consumed) {
+    size_t bytes_to_read = min(iov[iov_index].iov_len,
+                               decompressed_headers_.length() - bytes_consumed);
+    char* iov_ptr = static_cast<char*>(iov[iov_index].iov_base);
+    memcpy(iov_ptr,
+           decompressed_headers_.data() + bytes_consumed, bytes_to_read);
+    bytes_consumed += bytes_to_read;
+    ++iov_index;
+  }
+  decompressed_headers_.erase(0, bytes_consumed);
+  if (FinishedReadingHeaders()) {
+    sequencer()->FlushBufferedFrames();
+  }
+  return bytes_consumed;
+}
+
+int QuicDataStream::GetReadableRegions(iovec* iov, size_t iov_len) {
+  if (FinishedReadingHeaders()) {
+    return sequencer()->GetReadableRegions(iov, iov_len);
+  }
+  if (iov_len == 0) {
+    return 0;
+  }
+  iov[0].iov_base = static_cast<void*>(
+      const_cast<char*>(decompressed_headers_.data()));
+  iov[0].iov_len = decompressed_headers_.length();
+  return 1;
+}
+
+bool QuicDataStream::IsDoneReading() const {
+  if (!headers_decompressed_ || !decompressed_headers_.empty()) {
+    return false;
+  }
+  return sequencer()->IsClosed();
+}
+
+bool QuicDataStream::HasBytesToRead() const {
+  return !decompressed_headers_.empty() || sequencer()->HasBytesToRead();
+}
+
+void QuicDataStream::set_priority(QuicPriority priority) {
+  DCHECK_EQ(0u, stream_bytes_written());
+  priority_ = priority;
+}
+
+QuicPriority QuicDataStream::EffectivePriority() const {
+  return priority();
+}
+
+uint32 QuicDataStream::ProcessRawData(const char* data, uint32 data_len) {
+  if (!FinishedReadingHeaders()) {
+    LOG(DFATAL) << "ProcessRawData called before headers have been finished";
+    return 0;
+  }
+  return ProcessData(data, data_len);
+}
+
+const IPEndPoint& QuicDataStream::GetPeerAddress() {
+  return session()->peer_address();
+}
+
+bool QuicDataStream::GetSSLInfo(SSLInfo* ssl_info) {
+  return session()->GetSSLInfo(ssl_info);
+}
+
+uint32 QuicDataStream::ProcessHeaderData() {
+  if (decompressed_headers_.empty()) {
+    return 0;
+  }
+
+  size_t bytes_processed = ProcessData(decompressed_headers_.data(),
+                                       decompressed_headers_.length());
+  if (bytes_processed == decompressed_headers_.length()) {
+    decompressed_headers_.clear();
+  } else {
+    decompressed_headers_ = decompressed_headers_.erase(0, bytes_processed);
+  }
+  return bytes_processed;
+}
+
+void QuicDataStream::OnStreamHeaders(StringPiece headers_data) {
+  headers_data.AppendToString(&decompressed_headers_);
+  ProcessHeaderData();
+}
+
+void QuicDataStream::OnStreamHeadersPriority(QuicPriority priority) {
+  DCHECK(session()->connection()->is_server());
+  set_priority(priority);
+}
+
+void QuicDataStream::OnStreamHeadersComplete(bool fin, size_t frame_len) {
+  headers_decompressed_ = true;
+  if (fin) {
+    sequencer()->OnStreamFrame(QuicStreamFrame(id(), fin, 0, IOVector()));
+  }
+  ProcessHeaderData();
+  if (FinishedReadingHeaders()) {
+    sequencer()->FlushBufferedFrames();
+  }
+}
+
+void QuicDataStream::OnClose() {
+  ReliableQuicStream::OnClose();
+
+  if (visitor_) {
+    Visitor* visitor = visitor_;
+    // Calling Visitor::OnClose() may result the destruction of the visitor,
+    // so we need to ensure we don't call it again.
+    visitor_ = nullptr;
+    visitor->OnClose(this);
+  }
+}
+
+bool QuicDataStream::FinishedReadingHeaders() {
+  return headers_decompressed_ && decompressed_headers_.empty();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_data_stream.h b/net/quic/quic_data_stream.h
new file mode 100644
index 0000000..1af9004
--- /dev/null
+++ b/net/quic/quic_data_stream.h
@@ -0,0 +1,147 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The base class for streams which deliver data to/from an application.
+// In each direction, the data on such a stream first contains compressed
+// headers then body data.
+
+#ifndef NET_QUIC_QUIC_DATA_STREAM_H_
+#define NET_QUIC_QUIC_DATA_STREAM_H_
+
+#include <sys/types.h>
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/iovec.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_stream_sequencer.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+namespace test {
+class QuicDataStreamPeer;
+class ReliableQuicStreamPeer;
+}  // namespace test
+
+class IPEndPoint;
+class QuicSession;
+class SSLInfo;
+
+// All this does right now is send data to subclasses via the sequencer.
+class NET_EXPORT_PRIVATE QuicDataStream : public ReliableQuicStream {
+ public:
+  // Visitor receives callbacks from the stream.
+  class Visitor {
+   public:
+    Visitor() {}
+
+    // Called when the stream is closed.
+    virtual void OnClose(QuicDataStream* stream) = 0;
+
+   protected:
+    virtual ~Visitor() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Visitor);
+  };
+
+  QuicDataStream(QuicStreamId id, QuicSession* session);
+
+  virtual ~QuicDataStream();
+
+  // ReliableQuicStream implementation
+  virtual void OnClose() OVERRIDE;
+  virtual uint32 ProcessRawData(const char* data, uint32 data_len) OVERRIDE;
+  // By default, this is the same as priority(), however it allows streams
+  // to temporarily alter effective priority.   For example if a SPDY stream has
+  // compressed but not written headers it can write the headers with a higher
+  // priority.
+  virtual QuicPriority EffectivePriority() const OVERRIDE;
+
+  // Overridden by subclasses to process data.  The headers will be delivered
+  // via OnStreamHeaders, so only data will be delivered through this method.
+  virtual uint32 ProcessData(const char* data, uint32 data_len) = 0;
+
+  // Called by the session when decompressed headers data is received
+  // for this stream.
+  // May be called multiple times, with each call providing additional headers
+  // data until OnStreamHeadersComplete is called.
+  virtual void OnStreamHeaders(base::StringPiece headers_data);
+
+  // Called by the session when headers with a priority have been received
+  // for this stream.  This method will only be called for server streams.
+  virtual void OnStreamHeadersPriority(QuicPriority priority);
+
+  // Called by the session when decompressed headers have been completely
+  // delilvered to this stream.  If |fin| is true, then this stream
+  // should be closed; no more data will be sent by the peer.
+  virtual void OnStreamHeadersComplete(bool fin, size_t frame_len);
+
+  // Writes the headers contained in |header_block| to the dedicated
+  // headers stream.
+  virtual size_t WriteHeaders(
+      const SpdyHeaderBlock& header_block,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // This block of functions wraps the sequencer's functions of the same
+  // name.  These methods return uncompressed data until that has
+  // been fully processed.  Then they simply delegate to the sequencer.
+  virtual size_t Readv(const struct iovec* iov, size_t iov_len);
+  virtual int GetReadableRegions(iovec* iov, size_t iov_len);
+  // Returns true when all data has been read from the peer, including the fin.
+  virtual bool IsDoneReading() const;
+  virtual bool HasBytesToRead() const;
+
+  void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+  bool headers_decompressed() const { return headers_decompressed_; }
+
+  const IPEndPoint& GetPeerAddress();
+
+  // Gets the SSL connection information.
+  bool GetSSLInfo(SSLInfo* ssl_info);
+
+ protected:
+  // Sets priority_ to priority.  This should only be called before bytes are
+  // written to the server.
+  void set_priority(QuicPriority priority);
+  // This is protected because external classes should use EffectivePriority
+  // instead.
+  QuicPriority priority() const { return priority_; }
+
+ private:
+  friend class test::QuicDataStreamPeer;
+  friend class test::ReliableQuicStreamPeer;
+  friend class QuicStreamUtils;
+
+  uint32 ProcessHeaderData();
+
+  bool FinishedReadingHeaders();
+
+  Visitor* visitor_;
+  // True if the headers have been completely decompresssed.
+  bool headers_decompressed_;
+  // The priority of the stream, once parsed.
+  QuicPriority priority_;
+  // Contains a copy of the decompressed headers until they are consumed
+  // via ProcessData or Readv.
+  string decompressed_headers_;
+  // True if an error was encountered during decompression.
+  bool decompression_failed_;
+  // True if the priority has been read, false otherwise.
+  bool priority_parsed_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicDataStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_DATA_STREAM_H_
diff --git a/net/quic/quic_data_stream_test.cc b/net/quic/quic_data_stream_test.cc
new file mode 100644
index 0000000..279dcd3
--- /dev/null
+++ b/net/quic/quic_data_stream_test.cc
@@ -0,0 +1,543 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_data_stream.h"
+
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/quic_write_blocked_list.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::StringPiece;
+using std::min;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const bool kIsServer = true;
+const bool kShouldProcessData = true;
+
+class TestStream : public QuicDataStream {
+ public:
+  TestStream(QuicStreamId id,
+             QuicSession* session,
+             bool should_process_data)
+      : QuicDataStream(id, session),
+        should_process_data_(should_process_data) {}
+
+  virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE {
+    EXPECT_NE(0u, data_len);
+    DVLOG(1) << "ProcessData data_len: " << data_len;
+    data_ += string(data, data_len);
+    return should_process_data_ ? data_len : 0;
+  }
+
+  using ReliableQuicStream::WriteOrBufferData;
+  using ReliableQuicStream::CloseReadSide;
+  using ReliableQuicStream::CloseWriteSide;
+
+  const string& data() const { return data_; }
+
+ private:
+  bool should_process_data_;
+  string data_;
+};
+
+class QuicDataStreamTest : public ::testing::TestWithParam<QuicVersion> {
+ public:
+  QuicDataStreamTest() {
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "https";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+  }
+
+  void Initialize(bool stream_should_process_data) {
+    connection_ = new testing::StrictMock<MockConnection>(
+        kIsServer, SupportedVersions(GetParam()));
+    session_.reset(new testing::StrictMock<MockSession>(connection_));
+    stream_.reset(new TestStream(kClientDataStreamId1, session_.get(),
+                                 stream_should_process_data));
+    stream2_.reset(new TestStream(kClientDataStreamId2, session_.get(),
+                                  stream_should_process_data));
+    write_blocked_list_ =
+        QuicSessionPeer::GetWriteBlockedStreams(session_.get());
+  }
+
+ protected:
+  MockConnection* connection_;
+  scoped_ptr<MockSession> session_;
+  scoped_ptr<TestStream> stream_;
+  scoped_ptr<TestStream> stream2_;
+  SpdyHeaderBlock headers_;
+  QuicWriteBlockedList* write_blocked_list_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicDataStreamTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicDataStreamTest, ProcessHeaders) {
+  Initialize(kShouldProcessData);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  stream_->OnStreamHeadersPriority(QuicUtils::HighestPriority());
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  EXPECT_EQ(QuicUtils::HighestPriority(), stream_->EffectivePriority());
+  EXPECT_EQ(headers, stream_->data());
+  EXPECT_FALSE(stream_->IsDoneReading());
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBody) {
+  Initialize(kShouldProcessData);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame);
+
+  EXPECT_EQ(headers + body, stream_->data());
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyFragments) {
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+
+  for (size_t fragment_size = 1; fragment_size < body.size();
+       ++fragment_size) {
+    Initialize(kShouldProcessData);
+    for (size_t offset = 0; offset < headers.size();
+         offset += fragment_size) {
+      size_t remaining_data = headers.size() - offset;
+      StringPiece fragment(headers.data() + offset,
+                           min(fragment_size, remaining_data));
+      stream_->OnStreamHeaders(fragment);
+    }
+    stream_->OnStreamHeadersComplete(false, headers.size());
+    for (size_t offset = 0; offset < body.size(); offset += fragment_size) {
+      size_t remaining_data = body.size() - offset;
+      StringPiece fragment(body.data() + offset,
+                           min(fragment_size, remaining_data));
+      QuicStreamFrame frame(kClientDataStreamId1, false, offset,
+                            MakeIOVector(fragment));
+      stream_->OnStreamFrame(frame);
+    }
+    ASSERT_EQ(headers + body,
+              stream_->data()) << "fragment_size: " << fragment_size;
+  }
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyFragmentsSplit) {
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+
+  for (size_t split_point = 1; split_point < body.size() - 1; ++split_point) {
+    Initialize(kShouldProcessData);
+    StringPiece headers1(headers.data(), split_point);
+    stream_->OnStreamHeaders(headers1);
+
+    StringPiece headers2(headers.data() + split_point,
+                         headers.size() - split_point);
+    stream_->OnStreamHeaders(headers2);
+    stream_->OnStreamHeadersComplete(false, headers.size());
+
+    StringPiece fragment1(body.data(), split_point);
+    QuicStreamFrame frame1(kClientDataStreamId1, false, 0,
+                           MakeIOVector(fragment1));
+    stream_->OnStreamFrame(frame1);
+
+    StringPiece fragment2(body.data() + split_point,
+                          body.size() - split_point);
+    QuicStreamFrame frame2(kClientDataStreamId1, false, split_point,
+                           MakeIOVector(fragment2));
+    stream_->OnStreamFrame(frame2);
+
+    ASSERT_EQ(headers + body,
+              stream_->data()) << "split_point: " << split_point;
+  }
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyReadv) {
+  Initialize(!kShouldProcessData);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame);
+
+  char buffer[2048];
+  ASSERT_LT(headers.length() + body.length(), arraysize(buffer));
+  struct iovec vec;
+  vec.iov_base = buffer;
+  vec.iov_len = arraysize(buffer);
+
+  size_t bytes_read = stream_->Readv(&vec, 1);
+  EXPECT_EQ(headers.length(), bytes_read);
+  EXPECT_EQ(headers, string(buffer, bytes_read));
+
+  bytes_read = stream_->Readv(&vec, 1);
+  EXPECT_EQ(body.length(), bytes_read);
+  EXPECT_EQ(body, string(buffer, bytes_read));
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
+  Initialize(!kShouldProcessData);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame);
+
+  char buffer[1];
+  struct iovec vec;
+  vec.iov_base = buffer;
+  vec.iov_len = arraysize(buffer);
+
+  string data = headers + body;
+  for (size_t i = 0; i < data.length(); ++i) {
+    size_t bytes_read = stream_->Readv(&vec, 1);
+    ASSERT_EQ(1u, bytes_read);
+    EXPECT_EQ(data.data()[i], buffer[0]);
+  }
+}
+
+TEST_P(QuicDataStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
+  Initialize(!kShouldProcessData);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body = "this is the body";
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame);
+
+  char buffer1[1];
+  char buffer2[1];
+  struct iovec vec[2];
+  vec[0].iov_base = buffer1;
+  vec[0].iov_len = arraysize(buffer1);
+  vec[1].iov_base = buffer2;
+  vec[1].iov_len = arraysize(buffer2);
+  string data = headers + body;
+  for (size_t i = 0; i < data.length(); i += 2) {
+    size_t bytes_read = stream_->Readv(vec, 2);
+    ASSERT_EQ(2u, bytes_read) << i;
+    ASSERT_EQ(data.data()[i], buffer1[0]) << i;
+    ASSERT_EQ(data.data()[i + 1], buffer2[0]) << i;
+  }
+}
+
+TEST_P(QuicDataStreamTest, StreamFlowControlBlocked) {
+  // Tests that we send a BLOCKED frame to the peer when we attempt to write,
+  // but are flow control blocked.
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64 kWindow = 36;
+  QuicFlowControllerPeer::SetSendWindowOffset(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::SendWindowOffset(
+                         stream_->flow_controller()));
+
+  // Try to send more data than the flow control limit allows.
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body;
+  const uint64 kOverflow = 15;
+  GenerateBody(&body, kWindow + kOverflow);
+
+  EXPECT_CALL(*connection_, SendBlocked(kClientDataStreamId1));
+  EXPECT_CALL(*session_, WritevData(kClientDataStreamId1, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(kWindow, true)));
+  stream_->WriteOrBufferData(body, false, nullptr);
+
+  // Should have sent as much as possible, resulting in no send window left.
+  EXPECT_EQ(0u,
+            QuicFlowControllerPeer::SendWindowSize(stream_->flow_controller()));
+
+  // And we should have queued the overflowed data.
+  EXPECT_EQ(kOverflow,
+            ReliableQuicStreamPeer::SizeOfQueuedData(stream_.get()));
+}
+
+TEST_P(QuicDataStreamTest, StreamFlowControlNoWindowUpdateIfNotConsumed) {
+  // The flow control receive window decreases whenever we add new bytes to the
+  // sequencer, whether they are consumed immediately or buffered. However we
+  // only send WINDOW_UPDATE frames based on increasing number of bytes
+  // consumed.
+
+  // Don't process data - it will be buffered instead.
+  Initialize(!kShouldProcessData);
+
+  // Expect no WINDOW_UPDATE frames to be sent.
+  EXPECT_CALL(*connection_, SendWindowUpdate(_, _)).Times(0);
+
+  // Set a small flow control receive window.
+  const uint64 kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+                         stream_->flow_controller()));
+
+  // Stream receives enough data to fill a fraction of the receive window.
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body;
+  GenerateBody(&body, kWindow / 3);
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+
+  QuicStreamFrame frame1(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame1);
+  EXPECT_EQ(kWindow - (kWindow / 3), QuicFlowControllerPeer::ReceiveWindowSize(
+                                         stream_->flow_controller()));
+
+  // Now receive another frame which results in the receive window being over
+  // half full. This should all be buffered, decreasing the receive window but
+  // not sending WINDOW_UPDATE.
+  QuicStreamFrame frame2(kClientDataStreamId1, false, kWindow / 3,
+                         MakeIOVector(body));
+  stream_->OnStreamFrame(frame2);
+  EXPECT_EQ(
+      kWindow - (2 * kWindow / 3),
+      QuicFlowControllerPeer::ReceiveWindowSize(stream_->flow_controller()));
+}
+
+TEST_P(QuicDataStreamTest, StreamFlowControlWindowUpdate) {
+  // Tests that on receipt of data, the stream updates its receive window offset
+  // appropriately, and sends WINDOW_UPDATE frames when its receive window drops
+  // too low.
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64 kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowOffset(
+                         stream_->flow_controller()));
+
+  // Stream receives enough data to fill a fraction of the receive window.
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  string body;
+  GenerateBody(&body, kWindow / 3);
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+
+  QuicStreamFrame frame1(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame1);
+  EXPECT_EQ(kWindow - (kWindow / 3), QuicFlowControllerPeer::ReceiveWindowSize(
+                                         stream_->flow_controller()));
+
+  // Now receive another frame which results in the receive window being over
+  // half full.  This will trigger the stream to increase its receive window
+  // offset and send a WINDOW_UPDATE. The result will be again an available
+  // window of kWindow bytes.
+  QuicStreamFrame frame2(kClientDataStreamId1, false, kWindow / 3,
+                         MakeIOVector(body));
+  EXPECT_CALL(*connection_,
+              SendWindowUpdate(kClientDataStreamId1,
+                               QuicFlowControllerPeer::ReceiveWindowOffset(
+                                   stream_->flow_controller()) +
+                                   2 * kWindow / 3));
+  stream_->OnStreamFrame(frame2);
+  EXPECT_EQ(kWindow, QuicFlowControllerPeer::ReceiveWindowSize(
+                         stream_->flow_controller()));
+}
+
+TEST_P(QuicDataStreamTest, ConnectionFlowControlWindowUpdate) {
+  // Tests that on receipt of data, the connection updates its receive window
+  // offset appropriately, and sends WINDOW_UPDATE frames when its receive
+  // window drops too low.
+  if (GetParam() < QUIC_VERSION_19) {
+    return;
+  }
+  Initialize(kShouldProcessData);
+
+  // Set a small flow control limit for streams and connection.
+  const uint64 kWindow = 36;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream_->flow_controller(),
+                                              kWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream2_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(stream2_->flow_controller(),
+                                              kWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+                                                 kWindow);
+  QuicFlowControllerPeer::SetMaxReceiveWindow(session_->flow_controller(),
+                                              kWindow);
+
+  // Supply headers to both streams so that they are happy to receive data.
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  stream_->OnStreamHeaders(headers);
+  stream_->OnStreamHeadersComplete(false, headers.size());
+  stream2_->OnStreamHeaders(headers);
+  stream2_->OnStreamHeadersComplete(false, headers.size());
+
+  // Each stream gets a quarter window of data. This should not trigger a
+  // WINDOW_UPDATE for either stream, nor for the connection.
+  string body;
+  GenerateBody(&body, kWindow / 4);
+  QuicStreamFrame frame1(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  stream_->OnStreamFrame(frame1);
+  QuicStreamFrame frame2(kClientDataStreamId2, false, 0, MakeIOVector(body));
+  stream2_->OnStreamFrame(frame2);
+
+  // Now receive a further single byte on one stream - again this does not
+  // trigger a stream WINDOW_UPDATE, but now the connection flow control window
+  // is over half full and thus a connection WINDOW_UPDATE is sent.
+  EXPECT_CALL(*connection_, SendWindowUpdate(kClientDataStreamId1, _)).Times(0);
+  EXPECT_CALL(*connection_, SendWindowUpdate(kClientDataStreamId2, _)).Times(0);
+  EXPECT_CALL(*connection_,
+              SendWindowUpdate(0, QuicFlowControllerPeer::ReceiveWindowOffset(
+                                      session_->flow_controller()) +
+                                      1 + kWindow / 2));
+  QuicStreamFrame frame3(kClientDataStreamId1, false, (kWindow / 4),
+                         MakeIOVector("a"));
+  stream_->OnStreamFrame(frame3);
+}
+
+TEST_P(QuicDataStreamTest, StreamFlowControlViolation) {
+  // Tests that on if the peer sends too much data (i.e. violates the flow
+  // control protocol), then we terminate the connection.
+
+  // Stream should not process data, so that data gets buffered in the
+  // sequencer, triggering flow control limits.
+  Initialize(!kShouldProcessData);
+
+  // Set a small flow control limit.
+  const uint64 kWindow = 50;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kWindow);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+
+  // Receive data to overflow the window, violating flow control.
+  string body;
+  GenerateBody(&body, kWindow + 1);
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA));
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicDataStreamTest, ConnectionFlowControlViolation) {
+  // Tests that on if the peer sends too much data (i.e. violates the flow
+  // control protocol), at the connection level (rather than the stream level)
+  // then we terminate the connection.
+  if (GetParam() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Stream should not process data, so that data gets buffered in the
+  // sequencer, triggering flow control limits.
+  Initialize(!kShouldProcessData);
+
+  // Set a small flow control window on streams, and connection.
+  const uint64 kStreamWindow = 50;
+  const uint64 kConnectionWindow = 10;
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(),
+                                                 kStreamWindow);
+  QuicFlowControllerPeer::SetReceiveWindowOffset(session_->flow_controller(),
+                                                 kConnectionWindow);
+
+  string headers = SpdyUtils::SerializeUncompressedHeaders(headers_);
+  stream_->OnStreamHeaders(headers);
+  EXPECT_EQ(headers, stream_->data());
+  stream_->OnStreamHeadersComplete(false, headers.size());
+
+  // Send enough data to overflow the connection level flow control window.
+  string body;
+  GenerateBody(&body, kConnectionWindow + 1);
+  EXPECT_LT(body.size(),  kStreamWindow);
+  QuicStreamFrame frame(kClientDataStreamId1, false, 0, MakeIOVector(body));
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA));
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_P(QuicDataStreamTest, StreamFlowControlFinNotBlocked) {
+  // An attempt to write a FIN with no data should not be flow control blocked,
+  // even if the send window is 0.
+
+  Initialize(kShouldProcessData);
+
+  // Set a flow control limit of zero.
+  QuicFlowControllerPeer::SetReceiveWindowOffset(stream_->flow_controller(), 0);
+  EXPECT_EQ(0u, QuicFlowControllerPeer::ReceiveWindowOffset(
+                    stream_->flow_controller()));
+
+  // Send a frame with a FIN but no data. This should not be blocked.
+  string body = "";
+  bool fin = true;
+
+  EXPECT_CALL(*connection_, SendBlocked(kClientDataStreamId1)).Times(0);
+  EXPECT_CALL(*session_, WritevData(kClientDataStreamId1, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(0, fin)));
+
+  stream_->WriteOrBufferData(body, fin, nullptr);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_data_writer.cc b/net/quic/quic_data_writer.cc
new file mode 100644
index 0000000..11b6b55
--- /dev/null
+++ b/net/quic/quic_data_writer.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_data_writer.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::numeric_limits;
+
+namespace net {
+
+QuicDataWriter::QuicDataWriter(size_t size)
+    : buffer_(new char[size]),
+      capacity_(size),
+      length_(0) {
+}
+
+QuicDataWriter::~QuicDataWriter() {
+  delete[] buffer_;
+}
+
+char* QuicDataWriter::take() {
+  char* rv = buffer_;
+  buffer_ = nullptr;
+  capacity_ = 0;
+  length_ = 0;
+  return rv;
+}
+
+bool QuicDataWriter::WriteUInt8(uint8 value) {
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt16(uint16 value) {
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt32(uint32 value) {
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt48(uint64 value) {
+  uint32 hi = value >> 32;
+  uint32 lo = value & GG_UINT64_C(0x00000000FFFFFFFF);
+  return WriteUInt32(lo) && WriteUInt16(hi);
+}
+
+bool QuicDataWriter::WriteUInt64(uint64 value) {
+  return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUFloat16(uint64 value) {
+  uint16 result;
+  if (value < (GG_UINT64_C(1) << kUFloat16MantissaEffectiveBits)) {
+    // Fast path: either the value is denormalized, or has exponent zero.
+    // Both cases are represented by the value itself.
+    result = value;
+  } else if (value >= kUFloat16MaxValue) {
+    // Value is out of range; clamp it to the maximum representable.
+    result = numeric_limits<uint16>::max();
+  } else {
+    // The highest bit is between position 13 and 42 (zero-based), which
+    // corresponds to exponent 1-30. In the output, mantissa is from 0 to 10,
+    // hidden bit is 11 and exponent is 11 to 15. Shift the highest bit to 11
+    // and count the shifts.
+    uint16 exponent = 0;
+    for (uint16 offset = 16; offset > 0; offset /= 2) {
+      // Right-shift the value until the highest bit is in position 11.
+      // For offset of 16, 8, 4, 2 and 1 (binary search over 1-30),
+      // shift if the bit is at or above 11 + offset.
+      if (value >= (GG_UINT64_C(1) << (kUFloat16MantissaBits + offset))) {
+        exponent += offset;
+        value >>= offset;
+      }
+    }
+
+    DCHECK_GE(exponent, 1);
+    DCHECK_LE(exponent, kUFloat16MaxExponent);
+    DCHECK_GE(value, GG_UINT64_C(1) << kUFloat16MantissaBits);
+    DCHECK_LT(value, GG_UINT64_C(1) << kUFloat16MantissaEffectiveBits);
+
+    // Hidden bit (position 11) is set. We should remove it and increment the
+    // exponent. Equivalently, we just add it to the exponent.
+    // This hides the bit.
+    result = value + (exponent << kUFloat16MantissaBits);
+  }
+
+  return WriteBytes(&result, sizeof(result));
+}
+
+bool QuicDataWriter::WriteStringPiece16(StringPiece val) {
+  if (val.length() > numeric_limits<uint16>::max()) {
+    return false;
+  }
+  if (!WriteUInt16(val.size())) {
+    return false;
+  }
+  return WriteBytes(val.data(), val.size());
+}
+
+bool QuicDataWriter::WriteIOVector(const IOVector& data) {
+  char *dest = BeginWrite(data.TotalBufferSize());
+  if (!dest) {
+    return false;
+  }
+  for (size_t i = 0; i < data.Size(); ++i) {
+    WriteBytes(data.iovec()[i].iov_base,  data.iovec()[i].iov_len);
+  }
+
+  return true;
+}
+
+char* QuicDataWriter::BeginWrite(size_t length) {
+  if (length_ > capacity_) {
+    return nullptr;
+  }
+
+  if (capacity_ - length_ < length) {
+    return nullptr;
+  }
+
+#ifdef ARCH_CPU_64_BITS
+  DCHECK_LE(length, numeric_limits<uint32>::max());
+#endif
+
+  return buffer_ + length_;
+}
+
+bool QuicDataWriter::WriteBytes(const void* data, size_t data_len) {
+  char* dest = BeginWrite(data_len);
+  if (!dest) {
+    return false;
+  }
+
+  memcpy(dest, data, data_len);
+
+  length_ += data_len;
+  return true;
+}
+
+bool QuicDataWriter::WriteRepeatedByte(uint8 byte, size_t count) {
+  char* dest = BeginWrite(count);
+  if (!dest) {
+    return false;
+  }
+
+  memset(dest, byte, count);
+
+  length_ += count;
+  return true;
+}
+
+void QuicDataWriter::WritePadding() {
+  DCHECK_LE(length_, capacity_);
+  if (length_ > capacity_) {
+    return;
+  }
+  memset(buffer_ + length_, 0x00, capacity_ - length_);
+  length_ = capacity_;
+}
+
+bool QuicDataWriter::WriteUInt8ToOffset(uint8 value, size_t offset) {
+  if (offset >= capacity_) {
+    LOG(DFATAL) << "offset: " << offset << " >= capacity: " << capacity_;
+    return false;
+  }
+  size_t latched_length = length_;
+  length_ = offset;
+  bool success = WriteUInt8(value);
+  DCHECK_LE(length_, latched_length);
+  length_ = latched_length;
+  return success;
+}
+
+bool QuicDataWriter::WriteUInt32ToOffset(uint32 value, size_t offset) {
+  DCHECK_LT(offset, capacity_);
+  size_t latched_length = length_;
+  length_ = offset;
+  bool success = WriteUInt32(value);
+  DCHECK_LE(length_, latched_length);
+  length_ = latched_length;
+  return success;
+}
+
+bool QuicDataWriter::WriteUInt48ToOffset(uint64 value, size_t offset) {
+  DCHECK_LT(offset, capacity_);
+  size_t latched_length = length_;
+  length_ = offset;
+  bool success = WriteUInt48(value);
+  DCHECK_LE(length_, latched_length);
+  length_ = latched_length;
+  return success;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_data_writer.h b/net/quic/quic_data_writer.h
new file mode 100644
index 0000000..8204c3a
--- /dev/null
+++ b/net/quic/quic_data_writer.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_DATA_WRITER_H_
+#define NET_QUIC_QUIC_DATA_WRITER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// This class provides facilities for packing QUIC data.
+//
+// The QuicDataWriter supports appending primitive values (int, string, etc)
+// to a frame instance.  The QuicDataWriter grows its internal memory buffer
+// dynamically to hold the sequence of primitive values.   The internal memory
+// buffer is exposed as the "data" of the QuicDataWriter.
+class NET_EXPORT_PRIVATE QuicDataWriter {
+ public:
+  explicit QuicDataWriter(size_t length);
+
+  ~QuicDataWriter();
+
+  // Returns the size of the QuicDataWriter's data.
+  size_t length() const { return length_; }
+
+  // Takes the buffer from the QuicDataWriter.
+  char* take();
+
+  // Methods for adding to the payload.  These values are appended to the end
+  // of the QuicDataWriter payload. Note - binary integers are written in
+  // host byte order (little endian) not network byte order (big endian).
+  bool WriteUInt8(uint8 value);
+  bool WriteUInt16(uint16 value);
+  bool WriteUInt32(uint32 value);
+  bool WriteUInt48(uint64 value);
+  bool WriteUInt64(uint64 value);
+  // Write unsigned floating point corresponding to the value. Large values are
+  // clamped to the maximum representable (kUFloat16MaxValue). Values that can
+  // not be represented directly are rounded down.
+  bool WriteUFloat16(uint64 value);
+  bool WriteStringPiece16(base::StringPiece val);
+  bool WriteIOVector(const IOVector& data);
+  bool WriteBytes(const void* data, size_t data_len);
+  bool WriteRepeatedByte(uint8 byte, size_t count);
+  // Fills the remaining buffer with null characters.
+  void WritePadding();
+
+  // Methods for editing the payload at a specific offset, where the
+  // offset must be within the writer's capacity.
+  // Return true if there is enough space at that offset, false otherwise.
+  bool WriteUInt8ToOffset(uint8 value, size_t offset);
+  bool WriteUInt32ToOffset(uint32 value, size_t offset);
+  bool WriteUInt48ToOffset(uint64 value, size_t offset);
+
+  size_t capacity() const {
+    return capacity_;
+  }
+
+ private:
+  // Returns the location that the data should be written at, or nullptr if
+  // there is not enough room. Call EndWrite with the returned offset and the
+  // given length to pad out for the next write.
+  char* BeginWrite(size_t length);
+
+  char* buffer_;
+  size_t capacity_;  // Allocation size of payload (or -1 if buffer is const).
+  size_t length_;    // Current length of the buffer.
+
+  DISALLOW_COPY_AND_ASSIGN(QuicDataWriter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_DATA_WRITER_H_
diff --git a/net/quic/quic_data_writer_test.cc b/net/quic/quic_data_writer_test.cc
new file mode 100644
index 0000000..07346c7
--- /dev/null
+++ b/net/quic/quic_data_writer_test.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_data_writer.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicDataWriterTest, WriteUInt8ToOffset) {
+  QuicDataWriter writer(4);
+
+  writer.WriteUInt32(0xfefdfcfb);
+  EXPECT_TRUE(writer.WriteUInt8ToOffset(1, 0));
+  EXPECT_TRUE(writer.WriteUInt8ToOffset(2, 1));
+  EXPECT_TRUE(writer.WriteUInt8ToOffset(3, 2));
+  EXPECT_TRUE(writer.WriteUInt8ToOffset(4, 3));
+
+  scoped_ptr<char[]> data(writer.take());
+
+  EXPECT_EQ(1, data[0]);
+  EXPECT_EQ(2, data[1]);
+  EXPECT_EQ(3, data[2]);
+  EXPECT_EQ(4, data[3]);
+}
+
+TEST(QuicDataWriterDeathTest, WriteUInt8ToOffset) {
+  QuicDataWriter writer(4);
+
+  EXPECT_DFATAL(EXPECT_FALSE(writer.WriteUInt8ToOffset(5, 4)),
+                "offset: 4 >= capacity: 4");
+}
+
+TEST(QuicDataWriterTest, SanityCheckUFloat16Consts) {
+  // Check the arithmetic on the constants - otherwise the values below make
+  // no sense.
+  EXPECT_EQ(30, kUFloat16MaxExponent);
+  EXPECT_EQ(11, kUFloat16MantissaBits);
+  EXPECT_EQ(12, kUFloat16MantissaEffectiveBits);
+  EXPECT_EQ(GG_UINT64_C(0x3FFC0000000), kUFloat16MaxValue);
+}
+
+TEST(QuicDataWriterTest, WriteUFloat16) {
+  struct TestCase {
+    uint64 decoded;
+    uint16 encoded;
+  };
+  TestCase test_cases[] = {
+    // Small numbers represent themselves.
+    { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 6, 6 },
+    { 7, 7 }, { 15, 15 }, { 31, 31 }, { 42, 42 }, { 123, 123 }, { 1234, 1234 },
+    // Check transition through 2^11.
+    { 2046, 2046 }, { 2047, 2047 }, { 2048, 2048 }, { 2049, 2049 },
+    // Running out of mantissa at 2^12.
+    { 4094, 4094 }, { 4095, 4095 }, { 4096, 4096 }, { 4097, 4096 },
+    { 4098, 4097 }, { 4099, 4097 }, { 4100, 4098 }, { 4101, 4098 },
+    // Check transition through 2^13.
+    { 8190, 6143 }, { 8191, 6143 }, { 8192, 6144 }, { 8193, 6144 },
+    { 8194, 6144 }, { 8195, 6144 }, { 8196, 6145 }, { 8197, 6145 },
+    // Half-way through the exponents.
+    { 0x7FF8000, 0x87FF }, { 0x7FFFFFF, 0x87FF }, { 0x8000000, 0x8800 },
+    { 0xFFF0000, 0x8FFF }, { 0xFFFFFFF, 0x8FFF }, { 0x10000000, 0x9000 },
+    // Transition into the largest exponent.
+    { 0x1FFFFFFFFFE, 0xF7FF}, { 0x1FFFFFFFFFF, 0xF7FF},
+    { 0x20000000000, 0xF800}, { 0x20000000001, 0xF800},
+    { 0x2003FFFFFFE, 0xF800}, { 0x2003FFFFFFF, 0xF800},
+    { 0x20040000000, 0xF801}, { 0x20040000001, 0xF801},
+    // Transition into the max value and clamping.
+    { 0x3FF80000000, 0xFFFE}, { 0x3FFBFFFFFFF, 0xFFFE},
+    { 0x3FFC0000000, 0xFFFF}, { 0x3FFC0000001, 0xFFFF},
+    { 0x3FFFFFFFFFF, 0xFFFF}, { 0x40000000000, 0xFFFF},
+    { 0xFFFFFFFFFFFFFFFF, 0xFFFF},
+  };
+  int num_test_cases = sizeof(test_cases) / sizeof(test_cases[0]);
+
+  for (int i = 0; i < num_test_cases; ++i) {
+    QuicDataWriter writer(2);
+    EXPECT_TRUE(writer.WriteUFloat16(test_cases[i].decoded));
+    scoped_ptr<char[]> data(writer.take());
+    EXPECT_EQ(test_cases[i].encoded, *reinterpret_cast<uint16*>(data.get()));
+  }
+}
+
+TEST(QuicDataWriterTest, ReadUFloat16) {
+  struct TestCase {
+    uint64 decoded;
+    uint16 encoded;
+  };
+  TestCase test_cases[] = {
+    // There are fewer decoding test cases because encoding truncates, and
+    // decoding returns the smallest expansion.
+    // Small numbers represent themselves.
+    { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 6, 6 },
+    { 7, 7 }, { 15, 15 }, { 31, 31 }, { 42, 42 }, { 123, 123 }, { 1234, 1234 },
+    // Check transition through 2^11.
+    { 2046, 2046 }, { 2047, 2047 }, { 2048, 2048 }, { 2049, 2049 },
+    // Running out of mantissa at 2^12.
+    { 4094, 4094 }, { 4095, 4095 }, { 4096, 4096 },
+    { 4098, 4097 }, { 4100, 4098 },
+    // Check transition through 2^13.
+    { 8190, 6143 }, { 8192, 6144 }, { 8196, 6145 },
+    // Half-way through the exponents.
+    { 0x7FF8000, 0x87FF }, { 0x8000000, 0x8800 },
+    { 0xFFF0000, 0x8FFF }, { 0x10000000, 0x9000 },
+    // Transition into the largest exponent.
+    { 0x1FFE0000000, 0xF7FF}, { 0x20000000000, 0xF800},
+    { 0x20040000000, 0xF801},
+    // Transition into the max value.
+    { 0x3FF80000000, 0xFFFE}, { 0x3FFC0000000, 0xFFFF},
+  };
+  int num_test_cases = sizeof(test_cases) / sizeof(test_cases[0]);
+
+  for (int i = 0; i < num_test_cases; ++i) {
+    QuicDataReader reader(reinterpret_cast<char*>(&test_cases[i].encoded), 2);
+    uint64 value;
+    EXPECT_TRUE(reader.ReadUFloat16(&value));
+    EXPECT_EQ(test_cases[i].decoded, value);
+  }
+}
+
+TEST(QuicDataWriterTest, RoundTripUFloat16) {
+  // Just test all 16-bit encoded values. 0 and max already tested above.
+  uint64 previous_value = 0;
+  for (uint16 i = 1; i < 0xFFFF; ++i) {
+    // Read the two bytes.
+    QuicDataReader reader(reinterpret_cast<char*>(&i), 2);
+    uint64 value;
+    // All values must be decodable.
+    EXPECT_TRUE(reader.ReadUFloat16(&value));
+    // Check that small numbers represent themselves
+    if (i < 4097)
+      EXPECT_EQ(i, value);
+    // Check there's monotonic growth.
+    EXPECT_LT(previous_value, value);
+    // Check that precision is within 0.5% away from the denormals.
+    if (i > 2000)
+      EXPECT_GT(previous_value * 1005, value * 1000);
+    // Check we're always within the promised range.
+    EXPECT_LT(value, GG_UINT64_C(0x3FFC0000000));
+    previous_value = value;
+    QuicDataWriter writer(6);
+    EXPECT_TRUE(writer.WriteUFloat16(value - 1));
+    EXPECT_TRUE(writer.WriteUFloat16(value));
+    EXPECT_TRUE(writer.WriteUFloat16(value + 1));
+    scoped_ptr<char[]> data(writer.take());
+    // Check minimal decoding (previous decoding has previous encoding).
+    EXPECT_EQ(i-1, *reinterpret_cast<uint16*>(data.get()));
+    // Check roundtrip.
+    EXPECT_EQ(i, *reinterpret_cast<uint16*>(data.get() + 2));
+    // Check next decoding.
+    EXPECT_EQ(i < 4096? i+1 : i, *reinterpret_cast<uint16*>(data.get() + 4));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_default_packet_writer.cc b/net/quic/quic_default_packet_writer.cc
new file mode 100644
index 0000000..d851dfb
--- /dev/null
+++ b/net/quic/quic_default_packet_writer.cc
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_default_packet_writer.h"
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+QuicDefaultPacketWriter::QuicDefaultPacketWriter() : weak_factory_(this) {
+}
+
+QuicDefaultPacketWriter::QuicDefaultPacketWriter(DatagramClientSocket* socket)
+    : socket_(socket),
+      write_blocked_(false),
+      weak_factory_(this) {
+}
+
+QuicDefaultPacketWriter::~QuicDefaultPacketWriter() {}
+
+WriteResult QuicDefaultPacketWriter::WritePacket(
+    const char* buffer, size_t buf_len,
+    const net::IPAddressNumber& self_address,
+    const net::IPEndPoint& peer_address) {
+  scoped_refptr<StringIOBuffer> buf(
+      new StringIOBuffer(std::string(buffer, buf_len)));
+  DCHECK(!IsWriteBlocked());
+  int rv = socket_->Write(buf.get(),
+                          buf_len,
+                          base::Bind(&QuicDefaultPacketWriter::OnWriteComplete,
+                                     weak_factory_.GetWeakPtr()));
+  WriteStatus status = WRITE_STATUS_OK;
+  if (rv < 0) {
+    if (rv != ERR_IO_PENDING) {
+      UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.WriteError", -rv);
+      status = WRITE_STATUS_ERROR;
+    } else {
+      status = WRITE_STATUS_BLOCKED;
+      write_blocked_ = true;
+    }
+  }
+
+  return WriteResult(status, rv);
+}
+
+bool QuicDefaultPacketWriter::IsWriteBlockedDataBuffered() const {
+  // Chrome sockets' Write() methods buffer the data until the Write is
+  // permitted.
+  return true;
+}
+
+bool QuicDefaultPacketWriter::IsWriteBlocked() const {
+  return write_blocked_;
+}
+
+void QuicDefaultPacketWriter::SetWritable() {
+  write_blocked_ = false;
+}
+
+void QuicDefaultPacketWriter::OnWriteComplete(int rv) {
+  DCHECK_NE(rv, ERR_IO_PENDING);
+  write_blocked_ = false;
+  if (rv < 0) {
+    connection_->OnWriteError(rv);
+  }
+  connection_->OnCanWrite();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_default_packet_writer.h b/net/quic/quic_default_packet_writer.h
new file mode 100644
index 0000000..15eb0dd
--- /dev/null
+++ b/net/quic/quic_default_packet_writer.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_DEFAULT_PACKET_WRITER_H_
+#define NET_QUIC_QUIC_DEFAULT_PACKET_WRITER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_packet_writer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+struct WriteResult;
+
+// Chrome specific packet writer which uses a DatagramClientSocket for writing
+// data.
+class NET_EXPORT_PRIVATE QuicDefaultPacketWriter : public QuicPacketWriter {
+ public:
+  QuicDefaultPacketWriter();
+  explicit QuicDefaultPacketWriter(DatagramClientSocket* socket);
+  virtual ~QuicDefaultPacketWriter();
+
+  // QuicPacketWriter
+  virtual WriteResult WritePacket(const char* buffer,
+                                  size_t buf_len,
+                                  const IPAddressNumber& self_address,
+                                  const IPEndPoint& peer_address) OVERRIDE;
+  virtual bool IsWriteBlockedDataBuffered() const OVERRIDE;
+  virtual bool IsWriteBlocked() const OVERRIDE;
+  virtual void SetWritable() OVERRIDE;
+
+  void OnWriteComplete(int rv);
+  void SetConnection(QuicConnection* connection) {
+    connection_ = connection;
+  }
+
+ protected:
+  void set_write_blocked(bool is_blocked) {
+    write_blocked_ = is_blocked;
+  }
+
+ private:
+  DatagramClientSocket* socket_;
+  QuicConnection* connection_;
+
+  // Whether a write is currently in flight.
+  bool write_blocked_;
+
+  base::WeakPtrFactory<QuicDefaultPacketWriter> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicDefaultPacketWriter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_DEFAULT_PACKET_WRITER_H_
diff --git a/net/quic/quic_dispatcher.cc b/net/quic/quic_dispatcher.cc
new file mode 100644
index 0000000..a72faa6
--- /dev/null
+++ b/net/quic/quic_dispatcher.cc
@@ -0,0 +1,408 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_dispatcher.h"
+
+#include <errno.h>
+
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_per_connection_packet_writer.h"
+#include "net/quic/quic_time_wait_list_manager.h"
+#include "net/quic/quic_utils.h"
+
+namespace net {
+
+using base::StringPiece;
+using std::make_pair;
+using std::find;
+
+class DeleteSessionsAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit DeleteSessionsAlarm(QuicDispatcher* dispatcher)
+      : dispatcher_(dispatcher) {
+  }
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    dispatcher_->DeleteSessions();
+    return QuicTime::Zero();
+  }
+
+ private:
+  QuicDispatcher* dispatcher_;
+};
+
+class QuicDispatcher::QuicFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+  explicit QuicFramerVisitor(QuicDispatcher* dispatcher)
+      : dispatcher_(dispatcher),
+        connection_id_(0) {}
+
+  // QuicFramerVisitorInterface implementation
+  virtual void OnPacket() OVERRIDE {}
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) OVERRIDE {
+    connection_id_ = header.connection_id;
+    return dispatcher_->OnUnauthenticatedPublicHeader(header);
+  }
+  virtual bool OnUnauthenticatedHeader(
+      const QuicPacketHeader& header) OVERRIDE {
+    dispatcher_->OnUnauthenticatedHeader(header);
+    return false;
+  }
+  virtual void OnError(QuicFramer* framer) OVERRIDE {
+    DVLOG(1) << QuicUtils::ErrorToString(framer->error());
+  }
+
+  virtual bool OnProtocolVersionMismatch(
+      QuicVersion /*received_version*/) OVERRIDE {
+    if (dispatcher_->time_wait_list_manager()->IsConnectionIdInTimeWait(
+            connection_id_)) {
+      // Keep processing after protocol mismatch - this will be dealt with by
+      // the TimeWaitListManager.
+      return true;
+    } else {
+      DLOG(DFATAL) << "Version mismatch, connection ID (" << connection_id_
+                   << ") not in time wait list.";
+      return false;
+    }
+  }
+
+  // The following methods should never get called because we always return
+  // false from OnUnauthenticatedHeader().  As a result, we never process the
+  // payload of the packet.
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& /*packet*/) OVERRIDE {
+    DCHECK(false);
+  }
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& /*packet*/) OVERRIDE {
+    DCHECK(false);
+  }
+  virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE {
+    DCHECK(false);
+  }
+  virtual bool OnPacketHeader(const QuicPacketHeader& /*header*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual void OnRevivedPacket() OVERRIDE {
+    DCHECK(false);
+  }
+  virtual void OnFecProtectedPayload(StringPiece /*payload*/) OVERRIDE {
+    DCHECK(false);
+  }
+  virtual bool OnStreamFrame(const QuicStreamFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnAckFrame(const QuicAckFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnStopWaitingFrame(
+      const QuicStopWaitingFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnPingFrame(const QuicPingFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame & /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& /*frame*/) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& /*frame*/)
+      OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE {
+    DCHECK(false);
+    return false;
+  }
+  virtual void OnFecData(const QuicFecData& /*fec*/) OVERRIDE {
+    DCHECK(false);
+  }
+  virtual void OnPacketComplete() OVERRIDE {
+    DCHECK(false);
+  }
+
+ private:
+  QuicDispatcher* dispatcher_;
+
+  // Latched in OnUnauthenticatedPublicHeader for use later.
+  QuicConnectionId connection_id_;
+};
+
+QuicPacketWriter* QuicDispatcher::DefaultPacketWriterFactory::Create(
+    QuicServerPacketWriter* writer,
+    QuicConnection* connection) {
+  return new QuicPerConnectionPacketWriter(writer, connection);
+}
+
+QuicDispatcher::PacketWriterFactoryAdapter::PacketWriterFactoryAdapter(
+    QuicDispatcher* dispatcher)
+    : dispatcher_(dispatcher) {}
+
+QuicDispatcher::PacketWriterFactoryAdapter::~PacketWriterFactoryAdapter() {}
+
+QuicPacketWriter* QuicDispatcher::PacketWriterFactoryAdapter::Create(
+    QuicConnection* connection) const {
+  return dispatcher_->packet_writer_factory_->Create(
+      dispatcher_->writer_.get(),
+      connection);
+}
+
+QuicDispatcher::QuicDispatcher(const QuicConfig& config,
+                               const QuicCryptoServerConfig& crypto_config,
+                               const QuicVersionVector& supported_versions,
+                               PacketWriterFactory* packet_writer_factory,
+                               QuicConnectionHelperInterface* helper)
+    : config_(config),
+      crypto_config_(crypto_config),
+      helper_(helper),
+      delete_sessions_alarm_(
+          helper_->CreateAlarm(new DeleteSessionsAlarm(this))),
+      packet_writer_factory_(packet_writer_factory),
+      connection_writer_factory_(this),
+      supported_versions_(supported_versions),
+      current_packet_(nullptr),
+      framer_(supported_versions, /*unused*/ QuicTime::Zero(), true),
+      framer_visitor_(new QuicFramerVisitor(this)) {
+  framer_.set_visitor(framer_visitor_.get());
+}
+
+QuicDispatcher::~QuicDispatcher() {
+  STLDeleteValues(&session_map_);
+  STLDeleteElements(&closed_session_list_);
+}
+
+void QuicDispatcher::Initialize(QuicServerPacketWriter* writer) {
+  DCHECK(writer_ == nullptr);
+  writer_.reset(writer);
+  time_wait_list_manager_.reset(CreateQuicTimeWaitListManager());
+}
+
+void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address,
+                                   const IPEndPoint& client_address,
+                                   const QuicEncryptedPacket& packet) {
+  current_server_address_ = server_address;
+  current_client_address_ = client_address;
+  current_packet_ = &packet;
+  // ProcessPacket will cause the packet to be dispatched in
+  // OnUnauthenticatedPublicHeader, or sent to the time wait list manager
+  // in OnAuthenticatedHeader.
+  framer_.ProcessPacket(packet);
+  // TODO(rjshade): Return a status describing if/why a packet was dropped,
+  //                and log somehow.  Maybe expose as a varz.
+}
+
+bool QuicDispatcher::OnUnauthenticatedPublicHeader(
+    const QuicPacketPublicHeader& header) {
+  QuicSession* session = nullptr;
+
+  QuicConnectionId connection_id = header.connection_id;
+  SessionMap::iterator it = session_map_.find(connection_id);
+  if (it == session_map_.end()) {
+    if (header.reset_flag) {
+      return false;
+    }
+    if (time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id)) {
+      return HandlePacketForTimeWait(header);
+    }
+
+    // Ensure the packet has a version negotiation bit set before creating a new
+    // session for it.  All initial packets for a new connection are required to
+    // have the flag set.  Otherwise it may be a stray packet.
+    if (header.version_flag) {
+      session = CreateQuicSession(connection_id, current_server_address_,
+                                  current_client_address_);
+    }
+
+    if (session == nullptr) {
+      DVLOG(1) << "Failed to create session for " << connection_id;
+      // Add this connection_id fo the time-wait state, to safely reject future
+      // packets.
+
+      if (header.version_flag &&
+          !framer_.IsSupportedVersion(header.versions.front())) {
+        // TODO(ianswett): Produce a no-version version negotiation packet.
+        return false;
+      }
+
+      // Use the version in the packet if possible, otherwise assume the latest.
+      QuicVersion version = header.version_flag ? header.versions.front() :
+          supported_versions_.front();
+      time_wait_list_manager_->AddConnectionIdToTimeWait(connection_id, version,
+                                                         nullptr);
+      DCHECK(time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id));
+      return HandlePacketForTimeWait(header);
+    }
+    DVLOG(1) << "Created new session for " << connection_id;
+    session_map_.insert(make_pair(connection_id, session));
+  } else {
+    session = it->second;
+  }
+
+  session->connection()->ProcessUdpPacket(
+      current_server_address_, current_client_address_, *current_packet_);
+
+  // Do not parse the packet further.  The session will process it completely.
+  return false;
+}
+
+void QuicDispatcher::OnUnauthenticatedHeader(const QuicPacketHeader& header) {
+  DCHECK(time_wait_list_manager_->IsConnectionIdInTimeWait(
+      header.public_header.connection_id));
+  time_wait_list_manager_->ProcessPacket(current_server_address_,
+                                         current_client_address_,
+                                         header.public_header.connection_id,
+                                         header.packet_sequence_number,
+                                         *current_packet_);
+}
+
+void QuicDispatcher::CleanUpSession(SessionMap::iterator it) {
+  QuicConnection* connection = it->second->connection();
+  QuicEncryptedPacket* connection_close_packet =
+      connection->ReleaseConnectionClosePacket();
+  write_blocked_list_.erase(connection);
+  time_wait_list_manager_->AddConnectionIdToTimeWait(it->first,
+                                                     connection->version(),
+                                                     connection_close_packet);
+  session_map_.erase(it);
+}
+
+void QuicDispatcher::DeleteSessions() {
+  STLDeleteElements(&closed_session_list_);
+}
+
+void QuicDispatcher::OnCanWrite() {
+  // We finished a write: the socket should not be blocked.
+  writer_->SetWritable();
+
+  // Give all the blocked writers one chance to write, until we're blocked again
+  // or there's no work left.
+  while (!write_blocked_list_.empty() && !writer_->IsWriteBlocked()) {
+    QuicBlockedWriterInterface* blocked_writer =
+        write_blocked_list_.begin()->first;
+    write_blocked_list_.erase(write_blocked_list_.begin());
+    blocked_writer->OnCanWrite();
+  }
+}
+
+bool QuicDispatcher::HasPendingWrites() const {
+  return !write_blocked_list_.empty();
+}
+
+void QuicDispatcher::Shutdown() {
+  while (!session_map_.empty()) {
+    QuicSession* session = session_map_.begin()->second;
+    session->connection()->SendConnectionClose(QUIC_PEER_GOING_AWAY);
+    // Validate that the session removes itself from the session map on close.
+    DCHECK(session_map_.empty() || session_map_.begin()->second != session);
+  }
+  DeleteSessions();
+}
+
+void QuicDispatcher::OnConnectionClosed(QuicConnectionId connection_id,
+                                        QuicErrorCode error) {
+  SessionMap::iterator it = session_map_.find(connection_id);
+  if (it == session_map_.end()) {
+    LOG(DFATAL) << "ConnectionId " << connection_id
+                << " does not exist in the session map.  "
+                << "Error: " << QuicUtils::ErrorToString(error);
+    LOG(DFATAL) << base::debug::StackTrace().ToString();
+    return;
+  }
+  DVLOG_IF(1, error != QUIC_NO_ERROR) << "Closing connection ("
+                                      << connection_id
+                                      << ") due to error: "
+                                      << QuicUtils::ErrorToString(error);
+  if (closed_session_list_.empty()) {
+    delete_sessions_alarm_->Set(helper_->GetClock()->ApproximateNow());
+  }
+  closed_session_list_.push_back(it->second);
+  CleanUpSession(it);
+}
+
+void QuicDispatcher::OnWriteBlocked(
+    QuicBlockedWriterInterface* blocked_writer) {
+  if (!writer_->IsWriteBlocked()) {
+    LOG(DFATAL) <<
+        "QuicDispatcher::OnWriteBlocked called when the writer is not blocked.";
+    // Return without adding the connection to the blocked list, to avoid
+    // infinite loops in OnCanWrite.
+    return;
+  }
+  write_blocked_list_.insert(make_pair(blocked_writer, true));
+}
+
+QuicSession* QuicDispatcher::CreateQuicSession(
+    QuicConnectionId connection_id,
+    const IPEndPoint& server_address,
+    const IPEndPoint& client_address) {
+  QuicServerSession* session = new QuicServerSession(
+      config_,
+      CreateQuicConnection(connection_id, server_address, client_address),
+      this);
+  session->InitializeSession(crypto_config_);
+  return session;
+}
+
+QuicConnection* QuicDispatcher::CreateQuicConnection(
+    QuicConnectionId connection_id,
+    const IPEndPoint& server_address,
+    const IPEndPoint& client_address) {
+  return new QuicConnection(connection_id,
+                            client_address,
+                            helper_,
+                            connection_writer_factory_,
+                            /* owns_writer= */ true,
+                            /* is_server= */ true,
+                            supported_versions_);
+}
+
+QuicTimeWaitListManager* QuicDispatcher::CreateQuicTimeWaitListManager() {
+  return new QuicTimeWaitListManager(
+      writer_.get(), this, helper_, supported_versions());
+}
+
+bool QuicDispatcher::HandlePacketForTimeWait(
+    const QuicPacketPublicHeader& header) {
+  if (header.reset_flag) {
+    // Public reset packets do not have sequence numbers, so ignore the packet.
+    return false;
+  }
+
+  // Switch the framer to the correct version, so that the sequence number can
+  // be parsed correctly.
+  framer_.set_version(time_wait_list_manager_->GetQuicVersionFromConnectionId(
+      header.connection_id));
+
+  // Continue parsing the packet to extract the sequence number.  Then
+  // send it to the time wait manager in OnUnathenticatedHeader.
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_dispatcher.h b/net/quic/quic_dispatcher.h
new file mode 100644
index 0000000..d07c46c
--- /dev/null
+++ b/net/quic/quic_dispatcher.h
@@ -0,0 +1,253 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A server side dispatcher which dispatches a given client's data to their
+// stream.
+
+#ifndef NET_QUIC_QUIC_DISPATCHER_H_
+#define NET_QUIC_QUIC_DISPATCHER_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_packet_writer.h"
+#include "net/quic/quic_server_session.h"
+#include "net/quic/quic_time_wait_list_manager.h"
+
+namespace net {
+namespace test {
+class QuicDispatcherPeer;
+}  // namespace test
+
+class DeleteSessionsAlarm;
+class QuicConfig;
+class QuicCryptoServerConfig;
+class QuicSession;
+
+class ProcessPacketInterface {
+ public:
+  virtual ~ProcessPacketInterface() {}
+  virtual void ProcessPacket(const IPEndPoint& server_address,
+                             const IPEndPoint& client_address,
+                             const QuicEncryptedPacket& packet) = 0;
+};
+
+class QuicDispatcher : public QuicBlockedWriterInterface,
+                       public QuicServerSessionVisitor,
+                       public ProcessPacketInterface {
+ public:
+  // Creates per-connection packet writers out of the QuicDispatcher's shared
+  // QuicPacketWriter. The per-connection writers' IsWriteBlocked() state must
+  // always be the same as the shared writer's IsWriteBlocked(), or else the
+  // QuicDispatcher::OnCanWrite logic will not work. (This will hopefully be
+  // cleaned up for bug 16950226.)
+  class PacketWriterFactory {
+   public:
+    virtual ~PacketWriterFactory() {}
+
+    virtual QuicPacketWriter* Create(QuicServerPacketWriter* writer,
+                                     QuicConnection* connection) = 0;
+  };
+
+  // Creates ordinary QuicPerConnectionPacketWriter instances.
+  class DefaultPacketWriterFactory : public PacketWriterFactory {
+   public:
+    virtual ~DefaultPacketWriterFactory() {}
+
+    virtual QuicPacketWriter* Create(
+        QuicServerPacketWriter* writer,
+        QuicConnection* connection) OVERRIDE;
+  };
+
+  // Ideally we'd have a linked_hash_set: the  boolean is unused.
+  typedef linked_hash_map<QuicBlockedWriterInterface*, bool> WriteBlockedList;
+
+  // Due to the way delete_sessions_closure_ is registered, the Dispatcher must
+  // live until epoll_server Shutdown. |supported_versions| specifies the list
+  // of supported QUIC versions. Takes ownership of |packet_writer_factory|,
+  // which is used to create per-connection writers.
+  QuicDispatcher(const QuicConfig& config,
+                 const QuicCryptoServerConfig& crypto_config,
+                 const QuicVersionVector& supported_versions,
+                 PacketWriterFactory* packet_writer_factory,
+                 QuicConnectionHelperInterface* helper);
+
+  virtual ~QuicDispatcher();
+
+  // Takes ownership of the packet writer.
+  virtual void Initialize(QuicServerPacketWriter* writer);
+
+  // Process the incoming packet by creating a new session, passing it to
+  // an existing session, or passing it to the TimeWaitListManager.
+  virtual void ProcessPacket(const IPEndPoint& server_address,
+                             const IPEndPoint& client_address,
+                             const QuicEncryptedPacket& packet) OVERRIDE;
+
+  // Returns true if there's anything in the blocked writer list.
+  virtual bool HasPendingWrites() const;
+
+  // Sends ConnectionClose frames to all connected clients.
+  void Shutdown();
+
+  // QuicBlockedWriterInterface implementation:
+  // Called when the socket becomes writable to allow queued writes to happen.
+  virtual void OnCanWrite() OVERRIDE;
+
+  // QuicServerSessionVisitor interface implementation:
+  // Ensure that the closed connection is cleaned up asynchronously.
+  virtual void OnConnectionClosed(QuicConnectionId connection_id,
+                                  QuicErrorCode error) OVERRIDE;
+
+  // Queues the blocked writer for later resumption.
+  virtual void OnWriteBlocked(
+      QuicBlockedWriterInterface* blocked_writer) OVERRIDE;
+
+  typedef base::hash_map<QuicConnectionId, QuicSession*> SessionMap;
+
+  // Deletes all sessions on the closed session list and clears the list.
+  void DeleteSessions();
+
+  const SessionMap& session_map() const { return session_map_; }
+
+ protected:
+  virtual QuicSession* CreateQuicSession(QuicConnectionId connection_id,
+                                         const IPEndPoint& server_address,
+                                         const IPEndPoint& client_address);
+
+  virtual QuicConnection* CreateQuicConnection(
+      QuicConnectionId connection_id,
+      const IPEndPoint& server_address,
+      const IPEndPoint& client_address);
+
+  // Called by |framer_visitor_| when the public header has been parsed.
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header);
+
+  // Create and return the time wait list manager for this dispatcher, which
+  // will be owned by the dispatcher as time_wait_list_manager_
+  virtual QuicTimeWaitListManager* CreateQuicTimeWaitListManager();
+
+  // Replaces the packet writer with |writer|. Takes ownership of |writer|.
+  void set_writer(QuicServerPacketWriter* writer) {
+    writer_.reset(writer);
+  }
+
+  QuicTimeWaitListManager* time_wait_list_manager() {
+    return time_wait_list_manager_.get();
+  }
+
+  const QuicVersionVector& supported_versions() const {
+    return supported_versions_;
+  }
+
+  const IPEndPoint& current_server_address() {
+    return current_server_address_;
+  }
+  const IPEndPoint& current_client_address() {
+    return current_client_address_;
+  }
+  const QuicEncryptedPacket& current_packet() {
+    return *current_packet_;
+  }
+
+  const QuicConfig& config() const { return config_; }
+
+  const QuicCryptoServerConfig& crypto_config() const { return crypto_config_; }
+
+  QuicFramer* framer() { return &framer_; }
+
+  QuicConnectionHelperInterface* helper() { return helper_; }
+
+  QuicServerPacketWriter* writer() { return writer_.get(); }
+
+  const QuicConnection::PacketWriterFactory& connection_writer_factory() {
+    return connection_writer_factory_;
+  }
+
+ private:
+  class QuicFramerVisitor;
+  friend class net::test::QuicDispatcherPeer;
+
+  // An adapter that creates packet writers using the dispatcher's
+  // PacketWriterFactory and shared writer. Essentially, it just curries the
+  // writer argument away from QuicDispatcher::PacketWriterFactory.
+  class PacketWriterFactoryAdapter :
+    public QuicConnection::PacketWriterFactory {
+   public:
+    PacketWriterFactoryAdapter(QuicDispatcher* dispatcher);
+    virtual ~PacketWriterFactoryAdapter ();
+
+    virtual QuicPacketWriter* Create(QuicConnection* connection) const OVERRIDE;
+
+   private:
+    QuicDispatcher* dispatcher_;
+  };
+
+  // Called by |framer_visitor_| when the private header has been parsed
+  // of a data packet that is destined for the time wait manager.
+  void OnUnauthenticatedHeader(const QuicPacketHeader& header);
+
+  // Removes the session from the session map and write blocked list, and
+  // adds the ConnectionId to the time-wait list.
+  void CleanUpSession(SessionMap::iterator it);
+
+  bool HandlePacketForTimeWait(const QuicPacketPublicHeader& header);
+
+  const QuicConfig& config_;
+
+  const QuicCryptoServerConfig& crypto_config_;
+
+  // The list of connections waiting to write.
+  WriteBlockedList write_blocked_list_;
+
+  SessionMap session_map_;
+
+  // Entity that manages connection_ids in time wait state.
+  scoped_ptr<QuicTimeWaitListManager> time_wait_list_manager_;
+
+  // The helper used for all connections. Owned by the server.
+  QuicConnectionHelperInterface* helper_;
+
+  // An alarm which deletes closed sessions.
+  scoped_ptr<QuicAlarm> delete_sessions_alarm_;
+
+  // The list of closed but not-yet-deleted sessions.
+  std::list<QuicSession*> closed_session_list_;
+
+  // The writer to write to the socket with.
+  scoped_ptr<QuicServerPacketWriter> writer_;
+
+  // Used to create per-connection packet writers, not |writer_| itself.
+  scoped_ptr<PacketWriterFactory> packet_writer_factory_;
+
+  // Passed in to QuicConnection for it to create the per-connection writers
+  PacketWriterFactoryAdapter connection_writer_factory_;
+
+  // This vector contains QUIC versions which we currently support.
+  // This should be ordered such that the highest supported version is the first
+  // element, with subsequent elements in descending order (versions can be
+  // skipped as necessary).
+  const QuicVersionVector supported_versions_;
+
+  // Information about the packet currently being handled.
+  IPEndPoint current_client_address_;
+  IPEndPoint current_server_address_;
+  const QuicEncryptedPacket* current_packet_;
+
+  QuicFramer framer_;
+  scoped_ptr<QuicFramerVisitor> framer_visitor_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicDispatcher);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_DISPATCHER_H_
diff --git a/net/quic/quic_end_to_end_unittest.cc b/net/quic/quic_end_to_end_unittest.cc
new file mode 100644
index 0000000..a37bd19
--- /dev/null
+++ b/net/quic/quic_end_to_end_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/mapped_host_resolver.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_test_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_server.h"
+#include "net/tools/quic/test_tools/quic_in_memory_cache_peer.h"
+#include "net/tools/quic/test_tools/server_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+using base::StringPiece;
+using net::tools::QuicInMemoryCache;
+using net::tools::QuicServer;
+using net::tools::test::QuicInMemoryCachePeer;
+using net::tools::test::ServerThread;
+
+namespace net {
+namespace test {
+
+namespace {
+
+const char kResponseBody[] = "some arbitrary response body";
+
+// Factory for creating HttpTransactions, used by TestTransactionConsumer.
+class TestTransactionFactory : public HttpTransactionFactory {
+ public:
+  TestTransactionFactory(const HttpNetworkSession::Params& params)
+      : session_(new HttpNetworkSession(params)) {}
+
+  virtual ~TestTransactionFactory() {
+  }
+
+  // HttpTransactionFactory methods
+  virtual int CreateTransaction(RequestPriority priority,
+                                scoped_ptr<HttpTransaction>* trans) OVERRIDE {
+    trans->reset(new HttpNetworkTransaction(priority, session_.get()));
+    return OK;
+  }
+
+  virtual HttpCache* GetCache() OVERRIDE {
+    return nullptr;
+  }
+
+  virtual HttpNetworkSession* GetSession() OVERRIDE { return session_.get(); };
+
+ private:
+  scoped_refptr<HttpNetworkSession> session_;
+};
+
+}  // namespace
+
+class QuicEndToEndTest : public PlatformTest {
+ protected:
+  QuicEndToEndTest()
+      : host_resolver_impl_(CreateResolverImpl()),
+        host_resolver_(host_resolver_impl_.PassAs<HostResolver>()),
+        ssl_config_service_(new SSLConfigServiceDefaults),
+        proxy_service_(ProxyService::CreateDirect()),
+        auth_handler_factory_(
+            HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
+        strike_register_no_startup_period_(false) {
+    request_.method = "GET";
+    request_.url = GURL("http://www.google.com/");
+    request_.load_flags = 0;
+
+    params_.enable_quic = true;
+    params_.quic_clock = nullptr;
+    params_.quic_random = nullptr;
+    params_.host_resolver = &host_resolver_;
+    params_.cert_verifier = &cert_verifier_;
+    params_.transport_security_state = &transport_security_state_;
+    params_.proxy_service = proxy_service_.get();
+    params_.ssl_config_service = ssl_config_service_.get();
+    params_.http_auth_handler_factory = auth_handler_factory_.get();
+    params_.http_server_properties = http_server_properties.GetWeakPtr();
+  }
+
+  // Creates a mock host resolver in which www.google.com
+  // resolves to localhost.
+  static MockHostResolver* CreateResolverImpl() {
+    MockHostResolver* resolver = new MockHostResolver();
+    resolver->rules()->AddRule("www.google.com", "127.0.0.1");
+    return resolver;
+  }
+
+  virtual void SetUp() {
+    QuicInMemoryCachePeer::ResetForTests();
+    StartServer();
+
+    // Use a mapped host resolver so that request for www.google.com (port 80)
+    // reach the server running on localhost.
+    std::string map_rule = "MAP www.google.com www.google.com:" +
+        base::IntToString(server_thread_->GetPort());
+    EXPECT_TRUE(host_resolver_.AddRuleFromString(map_rule));
+
+    // To simplify the test, and avoid the race with the HTTP request, we force
+    // QUIC for these requests.
+    params_.origin_to_force_quic_on =
+        HostPortPair::FromString("www.google.com:80");
+
+    transaction_factory_.reset(new TestTransactionFactory(params_));
+  }
+
+  virtual void TearDown() {
+    StopServer();
+    QuicInMemoryCachePeer::ResetForTests();
+  }
+
+  // Starts the QUIC server listening on a random port.
+  void StartServer() {
+    net::IPAddressNumber ip;
+    CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &ip));
+    server_address_ = IPEndPoint(ip, 0);
+    server_config_.SetDefaults();
+    server_config_.SetInitialFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    server_config_.SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    server_config_.SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    server_thread_.reset(new ServerThread(
+         new QuicServer(server_config_, QuicSupportedVersions()),
+         server_address_,
+         strike_register_no_startup_period_));
+    server_thread_->Initialize();
+    server_address_ = IPEndPoint(server_address_.address(),
+                                 server_thread_->GetPort());
+    server_thread_->Start();
+    server_started_ = true;
+  }
+
+  // Stops the QUIC server.
+  void StopServer() {
+    if (!server_started_) {
+      return;
+    }
+    if (server_thread_.get()) {
+      server_thread_->Quit();
+      server_thread_->Join();
+    }
+  }
+
+  // Adds an entry to the cache used by the QUIC server to serve
+  // responses.
+  void AddToCache(const StringPiece& method,
+                  const StringPiece& path,
+                  const StringPiece& version,
+                  const StringPiece& response_code,
+                  const StringPiece& response_detail,
+                  const StringPiece& body) {
+    QuicInMemoryCache::GetInstance()->AddSimpleResponse(
+        method, path, version, response_code, response_detail, body);
+  }
+
+  // Populates |request_body_| with |length_| ASCII bytes.
+  void GenerateBody(size_t length) {
+    request_body_.clear();
+    request_body_.reserve(length);
+    for (size_t i = 0; i < length; ++i) {
+      request_body_.append(1, static_cast<char>(32 + i % (126 - 32)));
+    }
+  }
+
+  // Initializes |request_| for a post of |length| bytes.
+  void InitializePostRequest(size_t length) {
+    GenerateBody(length);
+    ScopedVector<UploadElementReader> element_readers;
+    element_readers.push_back(
+        new UploadBytesElementReader(request_body_.data(),
+                                     request_body_.length()));
+    upload_data_stream_.reset(new UploadDataStream(element_readers.Pass(), 0));
+    request_.method = "POST";
+    request_.url = GURL("http://www.google.com/");
+    request_.upload_data_stream = upload_data_stream_.get();
+    ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+  }
+
+  // Checks that |consumer| completed and received |status_line| and |body|.
+  void CheckResponse(const TestTransactionConsumer& consumer,
+                     const std::string& status_line,
+                     const std::string& body) {
+    ASSERT_TRUE(consumer.is_done());
+    EXPECT_EQ(OK, consumer.error());
+    EXPECT_EQ(status_line,
+              consumer.response_info()->headers->GetStatusLine());
+    EXPECT_EQ(body, consumer.content());
+  }
+
+  scoped_ptr<MockHostResolver> host_resolver_impl_;
+  MappedHostResolver host_resolver_;
+  MockCertVerifier cert_verifier_;
+  TransportSecurityState transport_security_state_;
+  scoped_refptr<SSLConfigServiceDefaults> ssl_config_service_;
+  scoped_ptr<ProxyService> proxy_service_;
+  scoped_ptr<HttpAuthHandlerFactory> auth_handler_factory_;
+  HttpServerPropertiesImpl http_server_properties;
+  HttpNetworkSession::Params params_;
+  scoped_ptr<TestTransactionFactory> transaction_factory_;
+  HttpRequestInfo request_;
+  std::string request_body_;
+  scoped_ptr<UploadDataStream> upload_data_stream_;
+  scoped_ptr<ServerThread> server_thread_;
+  IPEndPoint server_address_;
+  std::string server_hostname_;
+  QuicConfig server_config_;
+  bool server_started_;
+  bool strike_register_no_startup_period_;
+};
+
+TEST_F(QuicEndToEndTest, LargeGetWithNoPacketLoss) {
+  std::string response(10 * 1024, 'x');
+
+  AddToCache("GET", request_.url.spec(),
+             "HTTP/1.1", "200", "OK",
+             response);
+
+  TestTransactionConsumer consumer(DEFAULT_PRIORITY,
+                                   transaction_factory_.get());
+  consumer.Start(&request_, BoundNetLog());
+
+  // Will terminate when the last consumer completes.
+  base::MessageLoop::current()->Run();
+
+  CheckResponse(consumer, "HTTP/1.1 200 OK", response);
+}
+
+// http://crbug.com/307284
+TEST_F(QuicEndToEndTest, DISABLED_LargePostWithNoPacketLoss) {
+  InitializePostRequest(10 * 1024 * 1024);
+
+  AddToCache("POST", request_.url.spec(),
+             "HTTP/1.1", "200", "OK",
+             kResponseBody);
+
+  TestTransactionConsumer consumer(DEFAULT_PRIORITY,
+                                   transaction_factory_.get());
+  consumer.Start(&request_, BoundNetLog());
+
+  // Will terminate when the last consumer completes.
+  base::MessageLoop::current()->Run();
+
+  CheckResponse(consumer, "HTTP/1.1 200 OK", kResponseBody);
+}
+
+TEST_F(QuicEndToEndTest, LargePostWithPacketLoss) {
+  // FLAGS_fake_packet_loss_percentage = 30;
+  InitializePostRequest(1024 * 1024);
+
+  const char kResponseBody[] = "some really big response body";
+  AddToCache("POST", request_.url.spec(),
+             "HTTP/1.1", "200", "OK",
+             kResponseBody);
+
+  TestTransactionConsumer consumer(DEFAULT_PRIORITY,
+                                   transaction_factory_.get());
+  consumer.Start(&request_, BoundNetLog());
+
+  // Will terminate when the last consumer completes.
+  base::MessageLoop::current()->Run();
+
+  CheckResponse(consumer, "HTTP/1.1 200 OK", kResponseBody);
+}
+
+TEST_F(QuicEndToEndTest, UberTest) {
+  // FLAGS_fake_packet_loss_percentage = 30;
+
+  const char kResponseBody[] = "some really big response body";
+  AddToCache("GET", request_.url.spec(),
+             "HTTP/1.1", "200", "OK",
+             kResponseBody);
+
+  std::vector<TestTransactionConsumer*> consumers;
+  size_t num_requests = 100;
+  for (size_t i = 0; i < num_requests; ++i) {
+      TestTransactionConsumer* consumer =
+          new TestTransactionConsumer(DEFAULT_PRIORITY,
+                                      transaction_factory_.get());
+      consumers.push_back(consumer);
+      consumer->Start(&request_, BoundNetLog());
+  }
+
+  // Will terminate when the last consumer completes.
+  base::MessageLoop::current()->Run();
+
+  for (size_t i = 0; i < num_requests; ++i) {
+    CheckResponse(*consumers[i], "HTTP/1.1 200 OK", kResponseBody);
+  }
+  STLDeleteElements(&consumers);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_fec_group.cc b/net/quic/quic_fec_group.cc
new file mode 100644
index 0000000..344f216
--- /dev/null
+++ b/net/quic/quic_fec_group.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_fec_group.h"
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::numeric_limits;
+using std::set;
+
+namespace net {
+
+namespace {
+const QuicPacketSequenceNumber kNoSequenceNumber = kuint64max;
+}  // namespace
+
+QuicFecGroup::QuicFecGroup()
+    : min_protected_packet_(kNoSequenceNumber),
+      max_protected_packet_(kNoSequenceNumber),
+      payload_parity_len_(0),
+      effective_encryption_level_(NUM_ENCRYPTION_LEVELS) {
+}
+
+QuicFecGroup::~QuicFecGroup() {}
+
+bool QuicFecGroup::Update(EncryptionLevel encryption_level,
+                          const QuicPacketHeader& header,
+                          StringPiece decrypted_payload) {
+  if (received_packets_.count(header.packet_sequence_number) != 0) {
+    return false;
+  }
+  if (min_protected_packet_ != kNoSequenceNumber &&
+      max_protected_packet_ != kNoSequenceNumber &&
+      (header.packet_sequence_number < min_protected_packet_ ||
+       header.packet_sequence_number > max_protected_packet_)) {
+    DLOG(ERROR) << "FEC group does not cover received packet: "
+                << header.packet_sequence_number;
+    return false;
+  }
+  if (!UpdateParity(decrypted_payload)) {
+    return false;
+  }
+  received_packets_.insert(header.packet_sequence_number);
+  if (encryption_level < effective_encryption_level_) {
+    effective_encryption_level_ = encryption_level;
+  }
+  return true;
+}
+
+bool QuicFecGroup::UpdateFec(
+    EncryptionLevel encryption_level,
+    QuicPacketSequenceNumber fec_packet_sequence_number,
+    const QuicFecData& fec) {
+  if (min_protected_packet_ != kNoSequenceNumber) {
+    return false;
+  }
+  SequenceNumberSet::const_iterator it = received_packets_.begin();
+  while (it != received_packets_.end()) {
+    if ((*it < fec.fec_group) || (*it >= fec_packet_sequence_number)) {
+      DLOG(ERROR) << "FEC group does not cover received packet: " << *it;
+      return false;
+    }
+    ++it;
+  }
+  if (!UpdateParity(fec.redundancy)) {
+    return false;
+  }
+  min_protected_packet_ = fec.fec_group;
+  max_protected_packet_ = fec_packet_sequence_number - 1;
+  if (encryption_level < effective_encryption_level_) {
+    effective_encryption_level_ = encryption_level;
+  }
+  return true;
+}
+
+bool QuicFecGroup::CanRevive() const {
+  // We can revive if we're missing exactly 1 packet.
+  return NumMissingPackets() == 1;
+}
+
+bool QuicFecGroup::IsFinished() const {
+  // We are finished if we are not missing any packets.
+  return NumMissingPackets() == 0;
+}
+
+size_t QuicFecGroup::Revive(QuicPacketHeader* header,
+                            char* decrypted_payload,
+                            size_t decrypted_payload_len) {
+  if (!CanRevive()) {
+    return 0;
+  }
+
+  // Identify the packet sequence number to be resurrected.
+  QuicPacketSequenceNumber missing = kNoSequenceNumber;
+  for (QuicPacketSequenceNumber i = min_protected_packet_;
+       i <= max_protected_packet_; ++i) {
+    // Is this packet missing?
+    if (received_packets_.count(i) == 0) {
+      missing = i;
+      break;
+    }
+  }
+  DCHECK_NE(kNoSequenceNumber, missing);
+
+  DCHECK_LE(payload_parity_len_, decrypted_payload_len);
+  if (payload_parity_len_ > decrypted_payload_len) {
+    return 0;
+  }
+  for (size_t i = 0; i < payload_parity_len_; ++i) {
+    decrypted_payload[i] = payload_parity_[i];
+  }
+
+  header->packet_sequence_number = missing;
+  header->entropy_flag = false;  // Unknown entropy.
+
+  received_packets_.insert(missing);
+  return payload_parity_len_;
+}
+
+bool QuicFecGroup::ProtectsPacketsBefore(QuicPacketSequenceNumber num) const {
+  if (max_protected_packet_ != kNoSequenceNumber) {
+    return max_protected_packet_ < num;
+  }
+  // Since we might not yet have received the FEC packet, we must check
+  // the packets we have received.
+  return *received_packets_.begin() < num;
+}
+
+bool QuicFecGroup::UpdateParity(StringPiece payload) {
+  DCHECK_LE(payload.size(), kMaxPacketSize);
+  if (payload.size() > kMaxPacketSize) {
+    DLOG(ERROR) << "Illegal payload size: " << payload.size();
+    return false;
+  }
+  if (payload_parity_len_ < payload.size()) {
+    payload_parity_len_ = payload.size();
+  }
+  DCHECK_LE(payload.size(), kMaxPacketSize);
+  if (received_packets_.empty() &&
+      min_protected_packet_ == kNoSequenceNumber) {
+    // Initialize the parity to the value of this payload
+    memcpy(payload_parity_, payload.data(), payload.size());
+    if (payload.size() < kMaxPacketSize) {
+      // TODO(rch): expand as needed.
+      memset(payload_parity_ + payload.size(), 0,
+             kMaxPacketSize - payload.size());
+    }
+    return true;
+  }
+  // Update the parity by XORing in the data (padding with 0s if necessary).
+  for (size_t i = 0; i < kMaxPacketSize; ++i) {
+    uint8 byte = i < payload.size() ? payload[i] : 0x00;
+    payload_parity_[i] ^= byte;
+  }
+  return true;
+}
+
+size_t QuicFecGroup::NumMissingPackets() const {
+  if (min_protected_packet_ == kNoSequenceNumber)
+    return numeric_limits<size_t>::max();
+  return (max_protected_packet_ - min_protected_packet_ + 1) -
+      received_packets_.size();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_fec_group.h b/net/quic/quic_fec_group.h
new file mode 100644
index 0000000..718d09f
--- /dev/null
+++ b/net/quic/quic_fec_group.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Tracks information about an FEC group, including the packets
+// that have been seen, and the running parity.  Provided the ability
+// to revive a dropped packet.
+
+#ifndef NET_QUIC_QUIC_FEC_GROUP_H_
+#define NET_QUIC_QUIC_FEC_GROUP_H_
+
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicFecGroup {
+ public:
+  QuicFecGroup();
+  ~QuicFecGroup();
+
+  // Updates the FEC group based on the delivery of a data packet decrypted at
+  // |encryption_level|. Returns false if this packet has already been seen,
+  // true otherwise.
+  bool Update(EncryptionLevel encryption_level,
+              const QuicPacketHeader& header,
+              base::StringPiece decrypted_payload);
+
+  // Updates the FEC group based on the delivery of an FEC packet decrypted at
+  // |encryption_level|. Returns false if this packet has already been seen or
+  // if it does not claim to protect all the packets previously seen in this
+  // group.
+  bool UpdateFec(EncryptionLevel encryption_level,
+                 QuicPacketSequenceNumber fec_packet_sequence_number,
+                 const QuicFecData& fec);
+
+  // Returns true if a packet can be revived from this FEC group.
+  bool CanRevive() const;
+
+  // Returns true if all packets (FEC and data) from this FEC group have been
+  // seen or revived
+  bool IsFinished() const;
+
+  // Revives the missing packet from this FEC group.  This may return a packet
+  // that is null padded to a greater length than the original packet, but
+  // the framer will handle it correctly.  Returns the length of the data
+  // written to |decrypted_payload|, or 0 if the packet could not be revived.
+  size_t Revive(QuicPacketHeader* header,
+                char* decrypted_payload,
+                size_t decrypted_payload_len);
+
+  // Returns true of this FEC group protects any packets with sequence
+  // numbers less than |num|.
+  bool ProtectsPacketsBefore(QuicPacketSequenceNumber num) const;
+
+  const base::StringPiece payload_parity() const {
+    return base::StringPiece(payload_parity_, payload_parity_len_);
+  }
+
+  QuicPacketSequenceNumber min_protected_packet() const {
+    return min_protected_packet_;
+  }
+
+  size_t NumReceivedPackets() const {
+    return received_packets_.size();
+  }
+
+  // Returns the effective encryption level of the FEC group.
+  EncryptionLevel effective_encryption_level() const {
+    return effective_encryption_level_;
+  }
+
+ private:
+  bool UpdateParity(base::StringPiece payload);
+  // Returns the number of missing packets, or size_t max if the number
+  // of missing packets is not known.
+  size_t NumMissingPackets() const;
+
+  // Set of packets that we have recevied.
+  SequenceNumberSet received_packets_;
+  // Sequence number of the first protected packet in this group (the one
+  // with the lowest packet sequence number).  Will only be set once the FEC
+  // packet has been seen.
+  QuicPacketSequenceNumber min_protected_packet_;
+  // Sequence number of the last protected packet in this group (the one
+  // with the highest packet sequence number).  Will only be set once the FEC
+  // packet has been seen.
+  QuicPacketSequenceNumber max_protected_packet_;
+  // The cumulative parity calculation of all received packets.
+  char payload_parity_[kMaxPacketSize];
+  size_t payload_parity_len_;
+  // The effective encryption level, which is the lowest encryption level of
+  // the data and FEC in the group.
+  EncryptionLevel effective_encryption_level_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicFecGroup);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_FEC_GROUP_H_
diff --git a/net/quic/quic_fec_group_test.cc b/net/quic/quic_fec_group_test.cc
new file mode 100644
index 0000000..b9420dd
--- /dev/null
+++ b/net/quic/quic_fec_group_test.cc
@@ -0,0 +1,242 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_fec_group.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::_;
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const char* kData[] = {
+  "abc12345678",
+  "987defg",
+  "ghi12345",
+  "987jlkmno",
+  "mno4567890",
+  "789pqrstuvw",
+};
+
+const bool kEntropyFlag[] = {
+  false,
+  true,
+  true,
+  false,
+  true,
+  true,
+};
+
+}  // namespace
+
+class QuicFecGroupTest : public ::testing::Test {
+ protected:
+  void RunTest(size_t num_packets, size_t lost_packet, bool out_of_order) {
+    size_t max_len = strlen(kData[0]);
+    scoped_ptr<char[]> redundancy(new char[max_len]);
+    for (size_t packet = 0; packet < num_packets; ++packet) {
+      for (size_t i = 0; i < max_len; i++) {
+        if (packet == 0) {
+          // Initialize to the first packet.
+          redundancy[i] = kData[0][i];
+          continue;
+        }
+        // XOR in the remaining packets.
+        uint8 byte = i > strlen(kData[packet]) ? 0x00 : kData[packet][i];
+        redundancy[i] = redundancy[i] ^ byte;
+      }
+    }
+
+    QuicFecGroup group;
+
+    // If we're out of order, send the FEC packet in the position of the
+    // lost packet. Otherwise send all (non-missing) packets, then FEC.
+    if (out_of_order) {
+      // Update the FEC state for each non-lost packet.
+      for (size_t packet = 0; packet < num_packets; packet++) {
+        if (packet == lost_packet) {
+          ASSERT_FALSE(group.IsFinished());
+          QuicFecData fec;
+          fec.fec_group = 0;
+          fec.redundancy = StringPiece(redundancy.get(), strlen(kData[0]));
+          ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, num_packets,
+                                      fec));
+        } else {
+          QuicPacketHeader header;
+          header.packet_sequence_number = packet;
+          header.entropy_flag = kEntropyFlag[packet];
+          ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header,
+                                   kData[packet]));
+        }
+        ASSERT_TRUE(group.CanRevive() == (packet == num_packets - 1));
+      }
+    } else {
+      // Update the FEC state for each non-lost packet.
+      for (size_t packet = 0; packet < num_packets; packet++) {
+        if (packet == lost_packet) {
+          continue;
+        }
+
+        QuicPacketHeader header;
+        header.packet_sequence_number = packet;
+        header.entropy_flag = kEntropyFlag[packet];
+        ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header,
+                                 kData[packet]));
+        ASSERT_FALSE(group.CanRevive());
+      }
+
+      ASSERT_FALSE(group.IsFinished());
+      // Attempt to revive the missing packet.
+      QuicFecData fec;
+      fec.fec_group = 0;
+      fec.redundancy = StringPiece(redundancy.get(), strlen(kData[0]));
+
+      ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, num_packets,
+                                  fec));
+    }
+    QuicPacketHeader header;
+    char recovered[kMaxPacketSize];
+    ASSERT_TRUE(group.CanRevive());
+    size_t len = group.Revive(&header, recovered, arraysize(recovered));
+    ASSERT_NE(0u, len)
+        << "Failed to revive packet " << lost_packet << " out of "
+        << num_packets;
+    EXPECT_EQ(lost_packet, header.packet_sequence_number)
+        << "Failed to revive packet " << lost_packet << " out of "
+        << num_packets;
+    // Revived packets have an unknown entropy.
+    EXPECT_FALSE(header.entropy_flag);
+    ASSERT_GE(len, strlen(kData[lost_packet])) << "Incorrect length";
+    for (size_t i = 0; i < strlen(kData[lost_packet]); i++) {
+      EXPECT_EQ(kData[lost_packet][i], recovered[i]);
+    }
+    ASSERT_TRUE(group.IsFinished());
+  }
+};
+
+TEST_F(QuicFecGroupTest, UpdateAndRevive) {
+  RunTest(2, 0, false);
+  RunTest(2, 1, false);
+
+  RunTest(3, 0, false);
+  RunTest(3, 1, false);
+  RunTest(3, 2, false);
+}
+
+TEST_F(QuicFecGroupTest, UpdateAndReviveOutOfOrder) {
+  RunTest(2, 0, true);
+  RunTest(2, 1, true);
+
+  RunTest(3, 0, true);
+  RunTest(3, 1, true);
+  RunTest(3, 2, true);
+}
+
+TEST_F(QuicFecGroupTest, UpdateFecIfReceivedPacketIsNotCovered) {
+  char data1[] = "abc123";
+  char redundancy[arraysize(data1)];
+  for (size_t i = 0; i < arraysize(data1); i++) {
+    redundancy[i] = data1[i];
+  }
+
+  QuicFecGroup group;
+
+  QuicPacketHeader header;
+  header.packet_sequence_number = 3;
+  group.Update(ENCRYPTION_FORWARD_SECURE, header, data1);
+
+  QuicFecData fec;
+  fec.fec_group = 1;
+  fec.redundancy = redundancy;
+
+  header.packet_sequence_number = 2;
+  ASSERT_FALSE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, 2, fec));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBefore) {
+  QuicPacketHeader header;
+  header.packet_sequence_number = 3;
+
+  QuicFecGroup group;
+  ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[0]));
+
+  EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+  EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+  EXPECT_FALSE(group.ProtectsPacketsBefore(3));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBeforeWithSeveralPackets) {
+  QuicPacketHeader header;
+  header.packet_sequence_number = 3;
+
+  QuicFecGroup group;
+  ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[0]));
+
+  header.packet_sequence_number = 7;
+  ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[0]));
+
+  header.packet_sequence_number = 5;
+  ASSERT_TRUE(group.Update(ENCRYPTION_FORWARD_SECURE, header, kData[0]));
+
+  EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+  EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+  EXPECT_FALSE(group.ProtectsPacketsBefore(3));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(6));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(7));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(8));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(9));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBeforeWithFecData) {
+  QuicFecData fec;
+  fec.fec_group = 2;
+  fec.redundancy = kData[0];
+
+  QuicFecGroup group;
+  ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, 3, fec));
+
+  EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+  EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(3));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+  EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+TEST_F(QuicFecGroupTest, EffectiveEncryptionLevel) {
+  QuicFecGroup group;
+  EXPECT_EQ(NUM_ENCRYPTION_LEVELS, group.effective_encryption_level());
+
+  QuicPacketHeader header;
+  header.packet_sequence_number = 5;
+  ASSERT_TRUE(group.Update(ENCRYPTION_INITIAL, header, kData[0]));
+  EXPECT_EQ(ENCRYPTION_INITIAL, group.effective_encryption_level());
+
+  QuicFecData fec;
+  fec.fec_group = 0;
+  fec.redundancy = kData[0];
+  ASSERT_TRUE(group.UpdateFec(ENCRYPTION_FORWARD_SECURE, 7, fec));
+  EXPECT_EQ(ENCRYPTION_INITIAL, group.effective_encryption_level());
+
+  header.packet_sequence_number = 3;
+  ASSERT_TRUE(group.Update(ENCRYPTION_NONE, header, kData[0]));
+  EXPECT_EQ(ENCRYPTION_NONE, group.effective_encryption_level());
+}
+
+}  // namespace net
diff --git a/net/quic/quic_flags.cc b/net/quic/quic_flags.cc
new file mode 100644
index 0000000..864e12b
--- /dev/null
+++ b/net/quic/quic_flags.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_flags.h"
+
+// TODO(rtenneti): Remove this.
+// Do not flip this flag until the flakiness of the
+// net/tools/quic/end_to_end_test is fixed.
+// If true, then QUIC connections will track the retransmission history of a
+// packet so that an ack of a previous transmission will ack the data of all
+// other transmissions.
+bool FLAGS_track_retransmission_history = false;
+
+bool FLAGS_quic_allow_oversized_packets_for_test = false;
+
+// When true, the use time based loss detection instead of nack.
+bool FLAGS_quic_use_time_loss_detection = false;
+
+// If true, it will return as soon as an error is detected while validating
+// CHLO.
+bool FLAGS_use_early_return_when_verifying_chlo = true;
+
+// If true, QUIC crypto reject message will include the reasons for rejection.
+bool FLAGS_send_quic_crypto_reject_reason = false;
+
+// If true, QUIC connections will support FEC protection of data while sending
+// packets, to reduce latency of data delivery to the application. The client
+// must also request FEC protection for the server to use FEC.
+bool FLAGS_enable_quic_fec = false;
+
+// If true, a QUIC connection with too many unfinished streams will be closed.
+bool FLAGS_close_quic_connection_unfinished_streams_2 = false;
+
+// When true, defaults to BBR congestion control instead of Cubic.
+bool FLAGS_quic_use_bbr_congestion_control = false;
+
+// If true, the server will accept slightly more streams than the negotiated
+// limit.
+bool FLAGS_quic_allow_more_open_streams = false;
+
+// If true, then QUIC connections will only timeout when an alarm fires, never
+// when setting a timeout.
+bool FLAGS_quic_timeouts_only_from_alarms = true;
+
+// If true, then QUIC connections will set both idle and overall timeouts in a
+// single method.
+bool FLAGS_quic_unified_timeouts = false;
diff --git a/net/quic/quic_flags.h b/net/quic/quic_flags.h
new file mode 100644
index 0000000..f1fea1e
--- /dev/null
+++ b/net/quic/quic_flags.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_FLAGS_H_
+#define NET_QUIC_QUIC_FLAGS_H_
+
+#include "net/base/net_export.h"
+
+NET_EXPORT_PRIVATE extern bool FLAGS_track_retransmission_history;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_allow_oversized_packets_for_test;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_use_time_loss_detection;
+NET_EXPORT_PRIVATE extern bool FLAGS_use_early_return_when_verifying_chlo;
+NET_EXPORT_PRIVATE extern bool FLAGS_send_quic_crypto_reject_reason;
+NET_EXPORT_PRIVATE extern bool FLAGS_enable_quic_fec;
+NET_EXPORT_PRIVATE extern bool FLAGS_close_quic_connection_unfinished_streams_2;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_use_bbr_congestion_control;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_allow_more_open_streams;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_timeouts_only_from_alarms;
+NET_EXPORT_PRIVATE extern bool FLAGS_quic_unified_timeouts;
+
+#endif  // NET_QUIC_QUIC_FLAGS_H_
diff --git a/net/quic/quic_flow_controller.cc b/net/quic/quic_flow_controller.cc
new file mode 100644
index 0000000..b145280
--- /dev/null
+++ b/net/quic/quic_flow_controller.cc
@@ -0,0 +1,191 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_flow_controller.h"
+
+#include "base/basictypes.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+QuicFlowController::QuicFlowController(QuicConnection* connection,
+                                       QuicStreamId id,
+                                       bool is_server,
+                                       uint64 send_window_offset,
+                                       uint64 receive_window_offset,
+                                       uint64 max_receive_window)
+    : connection_(connection),
+      id_(id),
+      is_enabled_(true),
+      is_server_(is_server),
+      bytes_consumed_(0),
+      highest_received_byte_offset_(0),
+      bytes_sent_(0),
+      send_window_offset_(send_window_offset),
+      receive_window_offset_(receive_window_offset),
+      max_receive_window_(max_receive_window),
+      last_blocked_send_window_offset_(0) {
+  DVLOG(1) << ENDPOINT << "Created flow controller for stream " << id_
+           << ", setting initial receive window offset to: "
+           << receive_window_offset_
+           << ", max receive window to: "
+           << max_receive_window_
+           << ", setting send window offset to: " << send_window_offset_;
+}
+
+void QuicFlowController::AddBytesConsumed(uint64 bytes_consumed) {
+  if (!IsEnabled()) {
+    return;
+  }
+
+  bytes_consumed_ += bytes_consumed;
+  DVLOG(1) << ENDPOINT << "Stream " << id_ << " consumed: " << bytes_consumed_;
+
+  MaybeSendWindowUpdate();
+}
+
+bool QuicFlowController::UpdateHighestReceivedOffset(uint64 new_offset) {
+  if (!IsEnabled()) {
+    return false;
+  }
+
+  // Only update if offset has increased.
+  if (new_offset <= highest_received_byte_offset_) {
+    return false;
+  }
+
+  DVLOG(1) << ENDPOINT << "Stream " << id_
+           << " highest byte offset increased from: "
+           << highest_received_byte_offset_ << " to " << new_offset;
+  highest_received_byte_offset_ = new_offset;
+  return true;
+}
+
+void QuicFlowController::AddBytesSent(uint64 bytes_sent) {
+  if (!IsEnabled()) {
+    return;
+  }
+
+  if (bytes_sent_ + bytes_sent > send_window_offset_) {
+    LOG(DFATAL) << ENDPOINT << "Stream " << id_ << " Trying to send an extra "
+                << bytes_sent << " bytes, when bytes_sent = " << bytes_sent_
+                << ", and send_window_offset_ = " << send_window_offset_;
+    bytes_sent_ = send_window_offset_;
+
+    // This is an error on our side, close the connection as soon as possible.
+    connection_->SendConnectionClose(QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA);
+    return;
+  }
+
+  bytes_sent_ += bytes_sent;
+  DVLOG(1) << ENDPOINT << "Stream " << id_ << " sent: " << bytes_sent_;
+}
+
+bool QuicFlowController::FlowControlViolation() {
+  if (!IsEnabled()) {
+    return false;
+  }
+
+  if (highest_received_byte_offset_ > receive_window_offset_) {
+    LOG(ERROR) << ENDPOINT << "Flow control violation on stream "
+               << id_ << ", receive window offset: "
+               << receive_window_offset_
+               << ", highest received byte offset: "
+               << highest_received_byte_offset_;
+    return true;
+  }
+  return false;
+}
+
+void QuicFlowController::MaybeSendWindowUpdate() {
+  if (!IsEnabled()) {
+    return;
+  }
+
+  // Send WindowUpdate to increase receive window if
+  // (receive window offset - consumed bytes) < (max window / 2).
+  // This is behaviour copied from SPDY.
+  DCHECK_LT(bytes_consumed_, receive_window_offset_);
+  size_t consumed_window = receive_window_offset_ - bytes_consumed_;
+  size_t threshold = (max_receive_window_ / 2);
+
+  if (consumed_window < threshold) {
+    // Update our receive window.
+    receive_window_offset_ += (max_receive_window_ - consumed_window);
+
+    DVLOG(1) << ENDPOINT << "Sending WindowUpdate frame for stream " << id_
+             << ", consumed bytes: " << bytes_consumed_
+             << ", consumed window: " << consumed_window
+             << ", and threshold: " << threshold
+             << ", and max recvw: " << max_receive_window_
+             << ". New receive window offset is: " << receive_window_offset_;
+
+    // Inform the peer of our new receive window.
+    connection_->SendWindowUpdate(id_, receive_window_offset_);
+  }
+}
+
+void QuicFlowController::MaybeSendBlocked() {
+  if (!IsEnabled()) {
+    return;
+  }
+
+  if (SendWindowSize() == 0 &&
+      last_blocked_send_window_offset_ < send_window_offset_) {
+    DVLOG(1) << ENDPOINT << "Stream " << id_ << " is flow control blocked. "
+             << "Send window: " << SendWindowSize()
+             << ", bytes sent: " << bytes_sent_
+             << ", send limit: " << send_window_offset_;
+    // The entire send_window has been consumed, we are now flow control
+    // blocked.
+    connection_->SendBlocked(id_);
+
+    // Keep track of when we last sent a BLOCKED frame so that we only send one
+    // at a given send offset.
+    last_blocked_send_window_offset_ = send_window_offset_;
+  }
+}
+
+bool QuicFlowController::UpdateSendWindowOffset(uint64 new_send_window_offset) {
+  if (!IsEnabled()) {
+    return false;
+  }
+
+  // Only update if send window has increased.
+  if (new_send_window_offset <= send_window_offset_) {
+    return false;
+  }
+
+  DVLOG(1) << ENDPOINT << "UpdateSendWindowOffset for stream " << id_
+           << " with new offset " << new_send_window_offset
+           << " , current offset: " << send_window_offset_;
+
+  send_window_offset_ = new_send_window_offset;
+  return true;
+}
+
+void QuicFlowController::Disable() {
+  is_enabled_ = false;
+}
+
+bool QuicFlowController::IsEnabled() const {
+  return is_enabled_;
+}
+
+bool QuicFlowController::IsBlocked() const {
+  return IsEnabled() && SendWindowSize() == 0;
+}
+
+uint64 QuicFlowController::SendWindowSize() const {
+  if (bytes_sent_ > send_window_offset_) {
+    return 0;
+  }
+  return send_window_offset_ - bytes_sent_;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_flow_controller.h b/net/quic/quic_flow_controller.h
new file mode 100644
index 0000000..e5f5494
--- /dev/null
+++ b/net/quic/quic_flow_controller.h
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_FLOW_CONTROLLER_H_
+#define NET_QUIC_QUIC_FLOW_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicFlowControllerPeer;
+}  // namespace test
+
+class QuicConnection;
+
+const QuicStreamId kConnectionLevelId = 0;
+
+// QuicFlowController allows a QUIC stream or connection to perform flow
+// control. The stream/connection owns a QuicFlowController which keeps track of
+// bytes sent/received, can tell the owner if it is flow control blocked, and
+// can send WINDOW_UPDATE or BLOCKED frames when needed.
+class NET_EXPORT_PRIVATE QuicFlowController {
+ public:
+  QuicFlowController(QuicConnection* connection,
+                     QuicStreamId id,
+                     bool is_server,
+                     uint64 send_window_offset,
+                     uint64 receive_window_offset,
+                     uint64 max_receive_window);
+  ~QuicFlowController() {}
+
+  // Called when we see a new highest received byte offset from the peer, either
+  // via a data frame or a RST.
+  // Returns true if this call changes highest_received_byte_offset_, and false
+  // in the case where |new_offset| is <= highest_received_byte_offset_.
+  bool UpdateHighestReceivedOffset(uint64 new_offset);
+
+  // Called when bytes received from the peer are consumed locally. This may
+  // trigger the sending of a WINDOW_UPDATE frame using |connection|.
+  void AddBytesConsumed(uint64 bytes_consumed);
+
+  // Called when bytes are sent to the peer.
+  void AddBytesSent(uint64 bytes_sent);
+
+  // Set a new send window offset.
+  // Returns true if this changes send_window_offset_, and false in the case
+  // where |new_send_window| is <= send_window_offset_.
+  bool UpdateSendWindowOffset(uint64 new_send_window_offset);
+
+  // Returns the current available send window.
+  uint64 SendWindowSize() const;
+
+  // Send a BLOCKED frame if appropriate.
+  void MaybeSendBlocked();
+
+  // Disable flow control.
+  void Disable();
+
+  // Returns true if flow control is enabled.
+  bool IsEnabled() const;
+
+  // Returns true if flow control send limits have been reached.
+  bool IsBlocked() const;
+
+  // Returns true if flow control receive limits have been violated by the peer.
+  bool FlowControlViolation();
+
+  uint64 bytes_consumed() const { return bytes_consumed_; }
+
+  uint64 highest_received_byte_offset() const {
+    return highest_received_byte_offset_;
+  }
+
+ private:
+  friend class test::QuicFlowControllerPeer;
+
+  // Send a WINDOW_UPDATE frame if appropriate.
+  void MaybeSendWindowUpdate();
+
+  // The parent connection, used to send connection close on flow control
+  // violation, and WINDOW_UPDATE and BLOCKED frames when appropriate.
+  // Not owned.
+  QuicConnection* connection_;
+
+  // ID of stream this flow controller belongs to. This can be 0 if this is a
+  // connection level flow controller.
+  QuicStreamId id_;
+
+  // True if flow control is enabled.
+  bool is_enabled_;
+
+  // True if this is owned by a server.
+  bool is_server_;
+
+  // Track number of bytes received from the peer, which have been consumed
+  // locally.
+  uint64 bytes_consumed_;
+
+  // The highest byte offset we have seen from the peer. This could be the
+  // highest offset in a data frame, or a final value in a RST.
+  uint64 highest_received_byte_offset_;
+
+  // Tracks number of bytes sent to the peer.
+  uint64 bytes_sent_;
+
+  // The absolute offset in the outgoing byte stream. If this offset is reached
+  // then we become flow control blocked until we receive a WINDOW_UPDATE.
+  uint64 send_window_offset_;
+
+  // The absolute offset in the incoming byte stream. The peer should never send
+  // us bytes which are beyond this offset.
+  uint64 receive_window_offset_;
+
+  // Largest size the receive window can grow to.
+  uint64 max_receive_window_;
+
+  // Keep track of the last time we sent a BLOCKED frame. We should only send
+  // another when the number of bytes we have sent has changed.
+  uint64 last_blocked_send_window_offset_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicFlowController);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_FLOW_CONTROLLER_H_
diff --git a/net/quic/quic_flow_controller_test.cc b/net/quic/quic_flow_controller_test.cc
new file mode 100644
index 0000000..f960169
--- /dev/null
+++ b/net/quic/quic_flow_controller_test.cc
@@ -0,0 +1,158 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_flow_controller.h"
+
+#include "base/strings/stringprintf.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::StringPrintf;
+
+namespace net {
+namespace test {
+
+using ::testing::_;
+
+class QuicFlowControllerTest : public ::testing::Test {
+ public:
+  QuicFlowControllerTest()
+      : stream_id_(1234),
+        send_window_(kInitialSessionFlowControlWindowForTest),
+        receive_window_(kInitialSessionFlowControlWindowForTest),
+        max_receive_window_(kInitialSessionFlowControlWindowForTest),
+        connection_(false) {
+  }
+
+  void Initialize() {
+    flow_controller_.reset(new QuicFlowController(
+        &connection_, stream_id_, false, send_window_,
+        receive_window_, max_receive_window_));
+  }
+
+ protected:
+  QuicStreamId stream_id_;
+  uint64 send_window_;
+  uint64 receive_window_;
+  uint64 max_receive_window_;
+  scoped_ptr<QuicFlowController> flow_controller_;
+  MockConnection connection_;
+};
+
+TEST_F(QuicFlowControllerTest, SendingBytes) {
+  Initialize();
+
+  EXPECT_TRUE(flow_controller_->IsEnabled());
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_FALSE(flow_controller_->FlowControlViolation());
+  EXPECT_EQ(send_window_, flow_controller_->SendWindowSize());
+
+  // Send some bytes, but not enough to block.
+  flow_controller_->AddBytesSent(send_window_ / 2);
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_EQ(send_window_ / 2, flow_controller_->SendWindowSize());
+
+  // Send enough bytes to block.
+  flow_controller_->AddBytesSent(send_window_ / 2);
+  EXPECT_TRUE(flow_controller_->IsBlocked());
+  EXPECT_EQ(0u, flow_controller_->SendWindowSize());
+
+  // BLOCKED frame should get sent.
+  EXPECT_CALL(connection_, SendBlocked(stream_id_)).Times(1);
+  flow_controller_->MaybeSendBlocked();
+
+  // Update the send window, and verify this has unblocked.
+  EXPECT_TRUE(flow_controller_->UpdateSendWindowOffset(2 * send_window_));
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_EQ(send_window_, flow_controller_->SendWindowSize());
+
+  // Updating with a smaller offset doesn't change anything.
+  EXPECT_FALSE(flow_controller_->UpdateSendWindowOffset(send_window_ / 10));
+  EXPECT_EQ(send_window_, flow_controller_->SendWindowSize());
+
+  // Try to send more bytes, violating flow control.
+  EXPECT_CALL(connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA));
+  EXPECT_DFATAL(
+      flow_controller_->AddBytesSent(send_window_ * 10),
+      StringPrintf("Trying to send an extra %d bytes",
+                   static_cast<int>(send_window_ * 10)));
+  EXPECT_TRUE(flow_controller_->IsBlocked());
+  EXPECT_EQ(0u, flow_controller_->SendWindowSize());
+}
+
+TEST_F(QuicFlowControllerTest, ReceivingBytes) {
+  Initialize();
+
+  EXPECT_TRUE(flow_controller_->IsEnabled());
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_FALSE(flow_controller_->FlowControlViolation());
+  EXPECT_EQ(kInitialSessionFlowControlWindowForTest,
+            QuicFlowControllerPeer::ReceiveWindowSize(flow_controller_.get()));
+
+  // Receive some bytes, updating highest received offset, but not enough to
+  // fill flow control receive window.
+  EXPECT_TRUE(
+      flow_controller_->UpdateHighestReceivedOffset(1 + receive_window_ / 2));
+  EXPECT_FALSE(flow_controller_->FlowControlViolation());
+  EXPECT_EQ((receive_window_ / 2) - 1,
+            QuicFlowControllerPeer::ReceiveWindowSize(flow_controller_.get()));
+
+  // Consume enough bytes to send a WINDOW_UPDATE frame.
+  EXPECT_CALL(connection_, SendWindowUpdate(stream_id_, _)).Times(1);
+
+  flow_controller_->AddBytesConsumed(1 + receive_window_ / 2);
+
+  // Result is that once again we have a fully open receive window.
+  EXPECT_FALSE(flow_controller_->FlowControlViolation());
+  EXPECT_EQ(kInitialSessionFlowControlWindowForTest,
+            QuicFlowControllerPeer::ReceiveWindowSize(flow_controller_.get()));
+}
+
+TEST_F(QuicFlowControllerTest, OnlySendBlockedFrameOncePerOffset) {
+  Initialize();
+
+  // Test that we don't send duplicate BLOCKED frames. We should only send one
+  // BLOCKED frame at a given send window offset.
+  EXPECT_TRUE(flow_controller_->IsEnabled());
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_FALSE(flow_controller_->FlowControlViolation());
+  EXPECT_EQ(send_window_, flow_controller_->SendWindowSize());
+
+  // Send enough bytes to block.
+  flow_controller_->AddBytesSent(send_window_);
+  EXPECT_TRUE(flow_controller_->IsBlocked());
+  EXPECT_EQ(0u, flow_controller_->SendWindowSize());
+
+  // Expect that 2 BLOCKED frames should get sent in total.
+  EXPECT_CALL(connection_, SendBlocked(stream_id_)).Times(2);
+
+  // BLOCKED frame should get sent.
+  flow_controller_->MaybeSendBlocked();
+
+  // BLOCKED frame should not get sent again until our send offset changes.
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+  flow_controller_->MaybeSendBlocked();
+
+  // Update the send window, then send enough bytes to block again.
+  EXPECT_TRUE(flow_controller_->UpdateSendWindowOffset(2 * send_window_));
+  EXPECT_FALSE(flow_controller_->IsBlocked());
+  EXPECT_EQ(send_window_, flow_controller_->SendWindowSize());
+  flow_controller_->AddBytesSent(send_window_);
+  EXPECT_TRUE(flow_controller_->IsBlocked());
+  EXPECT_EQ(0u, flow_controller_->SendWindowSize());
+
+  // BLOCKED frame should get sent as send offset has changed.
+  flow_controller_->MaybeSendBlocked();
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_framer.cc b/net/quic/quic_framer.cc
new file mode 100644
index 0000000..83c44c0
--- /dev/null
+++ b/net/quic/quic_framer.cc
@@ -0,0 +1,2325 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_framer.h"
+
+#include "base/containers/hash_tables.h"
+#include "base/stl_util.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake_message.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_data_writer.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_socket_address_coder.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::max;
+using std::min;
+using std::numeric_limits;
+using std::string;
+
+namespace net {
+
+namespace {
+
+// Mask to select the lowest 48 bits of a sequence number.
+const QuicPacketSequenceNumber k6ByteSequenceNumberMask =
+    GG_UINT64_C(0x0000FFFFFFFFFFFF);
+const QuicPacketSequenceNumber k4ByteSequenceNumberMask =
+    GG_UINT64_C(0x00000000FFFFFFFF);
+const QuicPacketSequenceNumber k2ByteSequenceNumberMask =
+    GG_UINT64_C(0x000000000000FFFF);
+const QuicPacketSequenceNumber k1ByteSequenceNumberMask =
+    GG_UINT64_C(0x00000000000000FF);
+
+const QuicConnectionId k1ByteConnectionIdMask = GG_UINT64_C(0x00000000000000FF);
+const QuicConnectionId k4ByteConnectionIdMask = GG_UINT64_C(0x00000000FFFFFFFF);
+
+// Number of bits the sequence number length bits are shifted from the right
+// edge of the public header.
+const uint8 kPublicHeaderSequenceNumberShift = 4;
+
+// New Frame Types, QUIC v. >= 10:
+// There are two interpretations for the Frame Type byte in the QUIC protocol,
+// resulting in two Frame Types: Special Frame Types and Regular Frame Types.
+//
+// Regular Frame Types use the Frame Type byte simply. Currently defined
+// Regular Frame Types are:
+// Padding            : 0b 00000000 (0x00)
+// ResetStream        : 0b 00000001 (0x01)
+// ConnectionClose    : 0b 00000010 (0x02)
+// GoAway             : 0b 00000011 (0x03)
+// WindowUpdate       : 0b 00000100 (0x04)
+// Blocked            : 0b 00000101 (0x05)
+//
+// Special Frame Types encode both a Frame Type and corresponding flags
+// all in the Frame Type byte. Currently defined Special Frame Types are:
+// Stream             : 0b 1xxxxxxx
+// Ack                : 0b 01xxxxxx
+// CongestionFeedback : 0b 001xxxxx
+//
+// Semantics of the flag bits above (the x bits) depends on the frame type.
+
+// Masks to determine if the frame type is a special use
+// and for specific special frame types.
+const uint8 kQuicFrameTypeSpecialMask = 0xE0;  // 0b 11100000
+const uint8 kQuicFrameTypeStreamMask = 0x80;
+const uint8 kQuicFrameTypeAckMask = 0x40;
+const uint8 kQuicFrameTypeCongestionFeedbackMask = 0x20;
+
+// Stream frame relative shifts and masks for interpreting the stream flags.
+// StreamID may be 1, 2, 3, or 4 bytes.
+const uint8 kQuicStreamIdShift = 2;
+const uint8 kQuicStreamIDLengthMask = 0x03;
+
+// Offset may be 0, 2, 3, 4, 5, 6, 7, 8 bytes.
+const uint8 kQuicStreamOffsetShift = 3;
+const uint8 kQuicStreamOffsetMask = 0x07;
+
+// Data length may be 0 or 2 bytes.
+const uint8 kQuicStreamDataLengthShift = 1;
+const uint8 kQuicStreamDataLengthMask = 0x01;
+
+// Fin bit may be set or not.
+const uint8 kQuicStreamFinShift = 1;
+const uint8 kQuicStreamFinMask = 0x01;
+
+// Sequence number size shift used in AckFrames.
+const uint8 kQuicSequenceNumberLengthShift = 2;
+
+// Acks may be truncated.
+const uint8 kQuicAckTruncatedShift = 1;
+const uint8 kQuicAckTruncatedMask = 0x01;
+
+// Acks may not have any nacks.
+const uint8 kQuicHasNacksMask = 0x01;
+
+// Returns the absolute value of the difference between |a| and |b|.
+QuicPacketSequenceNumber Delta(QuicPacketSequenceNumber a,
+                               QuicPacketSequenceNumber b) {
+  // Since these are unsigned numbers, we can't just return abs(a - b)
+  if (a < b) {
+    return b - a;
+  }
+  return a - b;
+}
+
+QuicPacketSequenceNumber ClosestTo(QuicPacketSequenceNumber target,
+                                   QuicPacketSequenceNumber a,
+                                   QuicPacketSequenceNumber b) {
+  return (Delta(target, a) < Delta(target, b)) ? a : b;
+}
+
+QuicSequenceNumberLength ReadSequenceNumberLength(uint8 flags) {
+  switch (flags & PACKET_FLAGS_6BYTE_SEQUENCE) {
+    case PACKET_FLAGS_6BYTE_SEQUENCE:
+      return PACKET_6BYTE_SEQUENCE_NUMBER;
+    case PACKET_FLAGS_4BYTE_SEQUENCE:
+      return PACKET_4BYTE_SEQUENCE_NUMBER;
+    case PACKET_FLAGS_2BYTE_SEQUENCE:
+      return PACKET_2BYTE_SEQUENCE_NUMBER;
+    case PACKET_FLAGS_1BYTE_SEQUENCE:
+      return PACKET_1BYTE_SEQUENCE_NUMBER;
+    default:
+      LOG(DFATAL) << "Unreachable case statement.";
+      return PACKET_6BYTE_SEQUENCE_NUMBER;
+  }
+}
+
+}  // namespace
+
+bool QuicFramerVisitorInterface::OnWindowUpdateFrame(
+    const QuicWindowUpdateFrame& frame) {
+  return true;
+}
+
+bool QuicFramerVisitorInterface::OnBlockedFrame(const QuicBlockedFrame& frame) {
+  return true;
+}
+
+QuicFramer::QuicFramer(const QuicVersionVector& supported_versions,
+                       QuicTime creation_time,
+                       bool is_server)
+    : visitor_(nullptr),
+      fec_builder_(nullptr),
+      entropy_calculator_(nullptr),
+      error_(QUIC_NO_ERROR),
+      last_sequence_number_(0),
+      last_serialized_connection_id_(0),
+      supported_versions_(supported_versions),
+      decrypter_level_(ENCRYPTION_NONE),
+      alternative_decrypter_level_(ENCRYPTION_NONE),
+      alternative_decrypter_latch_(false),
+      is_server_(is_server),
+      validate_flags_(true),
+      creation_time_(creation_time),
+      last_timestamp_(QuicTime::Delta::Zero()) {
+  DCHECK(!supported_versions.empty());
+  quic_version_ = supported_versions_[0];
+  decrypter_.reset(QuicDecrypter::Create(kNULL));
+  encrypter_[ENCRYPTION_NONE].reset(QuicEncrypter::Create(kNULL));
+}
+
+QuicFramer::~QuicFramer() {}
+
+// static
+size_t QuicFramer::GetMinStreamFrameSize(QuicStreamId stream_id,
+                                         QuicStreamOffset offset,
+                                         bool last_frame_in_packet,
+                                         InFecGroup is_in_fec_group) {
+  bool no_stream_frame_length = last_frame_in_packet &&
+                                is_in_fec_group == NOT_IN_FEC_GROUP;
+  return kQuicFrameTypeSize + GetStreamIdSize(stream_id) +
+      GetStreamOffsetSize(offset) +
+      (no_stream_frame_length ? 0 : kQuicStreamPayloadLengthSize);
+}
+
+// static
+size_t QuicFramer::GetMinAckFrameSize(
+    QuicSequenceNumberLength sequence_number_length,
+    QuicSequenceNumberLength largest_observed_length) {
+  return kQuicFrameTypeSize + kQuicEntropyHashSize +
+      largest_observed_length + kQuicDeltaTimeLargestObservedSize;
+}
+
+// static
+size_t QuicFramer::GetStopWaitingFrameSize(
+    QuicSequenceNumberLength sequence_number_length) {
+  return kQuicFrameTypeSize + kQuicEntropyHashSize +
+      sequence_number_length;
+}
+
+// static
+size_t QuicFramer::GetMinRstStreamFrameSize() {
+  return kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+      kQuicMaxStreamOffsetSize + kQuicErrorCodeSize +
+      kQuicErrorDetailsLengthSize;
+}
+
+// static
+size_t QuicFramer::GetMinConnectionCloseFrameSize() {
+  return kQuicFrameTypeSize + kQuicErrorCodeSize + kQuicErrorDetailsLengthSize;
+}
+
+// static
+size_t QuicFramer::GetMinGoAwayFrameSize() {
+  return kQuicFrameTypeSize + kQuicErrorCodeSize + kQuicErrorDetailsLengthSize +
+      kQuicMaxStreamIdSize;
+}
+
+// static
+size_t QuicFramer::GetWindowUpdateFrameSize() {
+  return kQuicFrameTypeSize + kQuicMaxStreamIdSize + kQuicMaxStreamOffsetSize;
+}
+
+// static
+size_t QuicFramer::GetBlockedFrameSize() {
+  return kQuicFrameTypeSize + kQuicMaxStreamIdSize;
+}
+
+// static
+size_t QuicFramer::GetStreamIdSize(QuicStreamId stream_id) {
+  // Sizes are 1 through 4 bytes.
+  for (int i = 1; i <= 4; ++i) {
+    stream_id >>= 8;
+    if (stream_id == 0) {
+      return i;
+    }
+  }
+  LOG(DFATAL) << "Failed to determine StreamIDSize.";
+  return 4;
+}
+
+// static
+size_t QuicFramer::GetStreamOffsetSize(QuicStreamOffset offset) {
+  // 0 is a special case.
+  if (offset == 0) {
+    return 0;
+  }
+  // 2 through 8 are the remaining sizes.
+  offset >>= 8;
+  for (int i = 2; i <= 8; ++i) {
+    offset >>= 8;
+    if (offset == 0) {
+      return i;
+    }
+  }
+  LOG(DFATAL) << "Failed to determine StreamOffsetSize.";
+  return 8;
+}
+
+// static
+size_t QuicFramer::GetVersionNegotiationPacketSize(size_t number_versions) {
+  return kPublicFlagsSize + PACKET_8BYTE_CONNECTION_ID +
+      number_versions * kQuicVersionSize;
+}
+
+bool QuicFramer::IsSupportedVersion(const QuicVersion version) const {
+  for (size_t i = 0; i < supported_versions_.size(); ++i) {
+    if (version == supported_versions_[i]) {
+      return true;
+    }
+  }
+  return false;
+}
+
+size_t QuicFramer::GetSerializedFrameLength(
+    const QuicFrame& frame,
+    size_t free_bytes,
+    bool first_frame,
+    bool last_frame,
+    InFecGroup is_in_fec_group,
+    QuicSequenceNumberLength sequence_number_length) {
+  if (frame.type == PADDING_FRAME) {
+    // PADDING implies end of packet.
+    return free_bytes;
+  }
+  size_t frame_len =
+      ComputeFrameLength(frame, last_frame, is_in_fec_group,
+                         sequence_number_length);
+  if (frame_len <= free_bytes) {
+    // Frame fits within packet. Note that acks may be truncated.
+    return frame_len;
+  }
+  // Only truncate the first frame in a packet, so if subsequent ones go
+  // over, stop including more frames.
+  if (!first_frame) {
+    return 0;
+  }
+  bool can_truncate = frame.type == ACK_FRAME &&
+      free_bytes >= GetMinAckFrameSize(PACKET_6BYTE_SEQUENCE_NUMBER,
+                                       PACKET_6BYTE_SEQUENCE_NUMBER);
+  if (can_truncate) {
+    // Truncate the frame so the packet will not exceed kMaxPacketSize.
+    // Note that we may not use every byte of the writer in this case.
+    DVLOG(1) << "Truncating large frame, free bytes: " << free_bytes;
+    return free_bytes;
+  }
+  if (!FLAGS_quic_allow_oversized_packets_for_test) {
+    return 0;
+  }
+  LOG(DFATAL) << "Packet size too small to fit frame.";
+  return frame_len;
+}
+
+QuicFramer::AckFrameInfo::AckFrameInfo() : max_delta(0) {}
+
+QuicFramer::AckFrameInfo::~AckFrameInfo() {}
+
+QuicPacketEntropyHash QuicFramer::GetPacketEntropyHash(
+    const QuicPacketHeader& header) const {
+  return header.entropy_flag << (header.packet_sequence_number % 8);
+}
+
+SerializedPacket QuicFramer::BuildDataPacket(
+    const QuicPacketHeader& header,
+    const QuicFrames& frames,
+    size_t packet_size) {
+  QuicDataWriter writer(packet_size);
+  const SerializedPacket kNoPacket(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0,
+                                   nullptr);
+  if (!AppendPacketHeader(header, &writer)) {
+    LOG(DFATAL) << "AppendPacketHeader failed";
+    return kNoPacket;
+  }
+
+  for (size_t i = 0; i < frames.size(); ++i) {
+    const QuicFrame& frame = frames[i];
+
+    // Determine if we should write stream frame length in header.
+    const bool no_stream_frame_length =
+        (header.is_in_fec_group == NOT_IN_FEC_GROUP) &&
+        (i == frames.size() - 1);
+    if (!AppendTypeByte(frame, no_stream_frame_length, &writer)) {
+      LOG(DFATAL) << "AppendTypeByte failed";
+      return kNoPacket;
+    }
+
+    switch (frame.type) {
+      case PADDING_FRAME:
+        writer.WritePadding();
+        break;
+      case STREAM_FRAME:
+        if (!AppendStreamFrame(
+            *frame.stream_frame, no_stream_frame_length, &writer)) {
+          LOG(DFATAL) << "AppendStreamFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case ACK_FRAME:
+        if (!AppendAckFrameAndTypeByte(
+                header, *frame.ack_frame, &writer)) {
+          LOG(DFATAL) << "AppendAckFrameAndTypeByte failed";
+          return kNoPacket;
+        }
+        break;
+      case CONGESTION_FEEDBACK_FRAME:
+        if (!AppendCongestionFeedbackFrame(
+                *frame.congestion_feedback_frame, &writer)) {
+          LOG(DFATAL) << "AppendCongestionFeedbackFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case STOP_WAITING_FRAME:
+        if (!AppendStopWaitingFrame(
+                header, *frame.stop_waiting_frame, &writer)) {
+          LOG(DFATAL) << "AppendStopWaitingFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case PING_FRAME:
+        // Ping has no payload.
+        break;
+      case RST_STREAM_FRAME:
+        if (!AppendRstStreamFrame(*frame.rst_stream_frame, &writer)) {
+          LOG(DFATAL) << "AppendRstStreamFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case CONNECTION_CLOSE_FRAME:
+        if (!AppendConnectionCloseFrame(
+                *frame.connection_close_frame, &writer)) {
+          LOG(DFATAL) << "AppendConnectionCloseFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case GOAWAY_FRAME:
+        if (!AppendGoAwayFrame(*frame.goaway_frame, &writer)) {
+          LOG(DFATAL) << "AppendGoAwayFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case WINDOW_UPDATE_FRAME:
+        if (!AppendWindowUpdateFrame(*frame.window_update_frame, &writer)) {
+          LOG(DFATAL) << "AppendWindowUpdateFrame failed";
+          return kNoPacket;
+        }
+        break;
+      case BLOCKED_FRAME:
+        if (!AppendBlockedFrame(*frame.blocked_frame, &writer)) {
+          LOG(DFATAL) << "AppendBlockedFrame failed";
+          return kNoPacket;
+        }
+        break;
+      default:
+        RaiseError(QUIC_INVALID_FRAME_DATA);
+        LOG(DFATAL) << "QUIC_INVALID_FRAME_DATA";
+        return kNoPacket;
+    }
+  }
+
+  // Save the length before writing, because take clears it.
+  const size_t len = writer.length();
+  // Less than or equal because truncated acks end up with max_plaintex_size
+  // length, even though they're typically slightly shorter.
+  DCHECK_LE(len, packet_size);
+  QuicPacket* packet = QuicPacket::NewDataPacket(
+      writer.take(), len, true, header.public_header.connection_id_length,
+      header.public_header.version_flag,
+      header.public_header.sequence_number_length);
+
+  if (fec_builder_) {
+    fec_builder_->OnBuiltFecProtectedPayload(header,
+                                             packet->FecProtectedData());
+  }
+
+  return SerializedPacket(header.packet_sequence_number,
+                          header.public_header.sequence_number_length, packet,
+                          GetPacketEntropyHash(header), nullptr);
+}
+
+SerializedPacket QuicFramer::BuildFecPacket(const QuicPacketHeader& header,
+                                            const QuicFecData& fec) {
+  DCHECK_EQ(IN_FEC_GROUP, header.is_in_fec_group);
+  DCHECK_NE(0u, header.fec_group);
+  size_t len = GetPacketHeaderSize(header);
+  len += fec.redundancy.length();
+
+  QuicDataWriter writer(len);
+  const SerializedPacket kNoPacket(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0,
+                                   nullptr);
+  if (!AppendPacketHeader(header, &writer)) {
+    LOG(DFATAL) << "AppendPacketHeader failed";
+    return kNoPacket;
+  }
+
+  if (!writer.WriteBytes(fec.redundancy.data(), fec.redundancy.length())) {
+    LOG(DFATAL) << "Failed to add FEC";
+    return kNoPacket;
+  }
+
+  return SerializedPacket(
+      header.packet_sequence_number,
+      header.public_header.sequence_number_length,
+      QuicPacket::NewFecPacket(writer.take(), len, true,
+                               header.public_header.connection_id_length,
+                               header.public_header.version_flag,
+                               header.public_header.sequence_number_length),
+      GetPacketEntropyHash(header), nullptr);
+}
+
+// static
+QuicEncryptedPacket* QuicFramer::BuildPublicResetPacket(
+    const QuicPublicResetPacket& packet) {
+  DCHECK(packet.public_header.reset_flag);
+
+  CryptoHandshakeMessage reset;
+  reset.set_tag(kPRST);
+  reset.SetValue(kRNON, packet.nonce_proof);
+  reset.SetValue(kRSEQ, packet.rejected_sequence_number);
+  if (!packet.client_address.address().empty()) {
+    // packet.client_address is non-empty.
+    QuicSocketAddressCoder address_coder(packet.client_address);
+    string serialized_address = address_coder.Encode();
+    if (serialized_address.empty()) {
+      return nullptr;
+    }
+    reset.SetStringPiece(kCADR, serialized_address);
+  }
+  const QuicData& reset_serialized = reset.GetSerialized();
+
+  size_t len =
+      kPublicFlagsSize + PACKET_8BYTE_CONNECTION_ID + reset_serialized.length();
+  QuicDataWriter writer(len);
+
+  uint8 flags = static_cast<uint8>(PACKET_PUBLIC_FLAGS_RST |
+                                   PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID);
+  if (!writer.WriteUInt8(flags)) {
+    return nullptr;
+  }
+
+  if (!writer.WriteUInt64(packet.public_header.connection_id)) {
+    return nullptr;
+  }
+
+  if (!writer.WriteBytes(reset_serialized.data(), reset_serialized.length())) {
+    return nullptr;
+  }
+
+  return new QuicEncryptedPacket(writer.take(), len, true);
+}
+
+QuicEncryptedPacket* QuicFramer::BuildVersionNegotiationPacket(
+    const QuicPacketPublicHeader& header,
+    const QuicVersionVector& supported_versions) {
+  DCHECK(header.version_flag);
+  size_t len = GetVersionNegotiationPacketSize(supported_versions.size());
+  QuicDataWriter writer(len);
+
+  uint8 flags = static_cast<uint8>(PACKET_PUBLIC_FLAGS_VERSION |
+                                   PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID);
+  if (!writer.WriteUInt8(flags)) {
+    return nullptr;
+  }
+
+  if (!writer.WriteUInt64(header.connection_id)) {
+    return nullptr;
+  }
+
+  for (size_t i = 0; i < supported_versions.size(); ++i) {
+    if (!writer.WriteUInt32(QuicVersionToQuicTag(supported_versions[i]))) {
+      return nullptr;
+    }
+  }
+
+  return new QuicEncryptedPacket(writer.take(), len, true);
+}
+
+bool QuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
+  DCHECK(!reader_.get());
+  reader_.reset(new QuicDataReader(packet.data(), packet.length()));
+
+  visitor_->OnPacket();
+
+  // First parse the public header.
+  QuicPacketPublicHeader public_header;
+  if (!ProcessPublicHeader(&public_header)) {
+    DLOG(WARNING) << "Unable to process public header.";
+    DCHECK_NE("", detailed_error_);
+    return RaiseError(QUIC_INVALID_PACKET_HEADER);
+  }
+
+  if (!visitor_->OnUnauthenticatedPublicHeader(public_header)) {
+    // The visitor suppresses further processing of the packet.
+    reader_.reset(nullptr);
+    return true;
+  }
+
+  if (is_server_ && public_header.version_flag &&
+      public_header.versions[0] != quic_version_) {
+    if (!visitor_->OnProtocolVersionMismatch(public_header.versions[0])) {
+      reader_.reset(nullptr);
+      return true;
+    }
+  }
+
+  bool rv;
+  if (!is_server_ && public_header.version_flag) {
+    rv = ProcessVersionNegotiationPacket(&public_header);
+  } else if (public_header.reset_flag) {
+    rv = ProcessPublicResetPacket(public_header);
+  } else {
+    rv = ProcessDataPacket(public_header, packet);
+  }
+
+  reader_.reset(nullptr);
+  return rv;
+}
+
+bool QuicFramer::ProcessVersionNegotiationPacket(
+    QuicPacketPublicHeader* public_header) {
+  DCHECK(!is_server_);
+  // Try reading at least once to raise error if the packet is invalid.
+  do {
+    QuicTag version;
+    if (!reader_->ReadBytes(&version, kQuicVersionSize)) {
+      set_detailed_error("Unable to read supported version in negotiation.");
+      return RaiseError(QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+    }
+    public_header->versions.push_back(QuicTagToQuicVersion(version));
+  } while (!reader_->IsDoneReading());
+
+  visitor_->OnVersionNegotiationPacket(*public_header);
+  return true;
+}
+
+bool QuicFramer::ProcessDataPacket(
+    const QuicPacketPublicHeader& public_header,
+    const QuicEncryptedPacket& packet) {
+  QuicPacketHeader header(public_header);
+  if (!ProcessPacketHeader(&header, packet)) {
+    DLOG(WARNING) << "Unable to process data packet header.";
+    return false;
+  }
+
+  if (!visitor_->OnPacketHeader(header)) {
+    // The visitor suppresses further processing of the packet.
+    return true;
+  }
+
+  if (packet.length() > kMaxPacketSize) {
+    DLOG(WARNING) << "Packet too large: " << packet.length();
+    return RaiseError(QUIC_PACKET_TOO_LARGE);
+  }
+
+  // Handle the payload.
+  if (!header.fec_flag) {
+    if (header.is_in_fec_group == IN_FEC_GROUP) {
+      StringPiece payload = reader_->PeekRemainingPayload();
+      visitor_->OnFecProtectedPayload(payload);
+    }
+    if (!ProcessFrameData(header)) {
+      DCHECK_NE(QUIC_NO_ERROR, error_);  // ProcessFrameData sets the error.
+      DLOG(WARNING) << "Unable to process frame data.";
+      return false;
+    }
+  } else {
+    QuicFecData fec_data;
+    fec_data.fec_group = header.fec_group;
+    fec_data.redundancy = reader_->ReadRemainingPayload();
+    visitor_->OnFecData(fec_data);
+  }
+
+  visitor_->OnPacketComplete();
+  return true;
+}
+
+bool QuicFramer::ProcessPublicResetPacket(
+    const QuicPacketPublicHeader& public_header) {
+  QuicPublicResetPacket packet(public_header);
+
+  scoped_ptr<CryptoHandshakeMessage> reset(
+      CryptoFramer::ParseMessage(reader_->ReadRemainingPayload()));
+  if (!reset.get()) {
+    set_detailed_error("Unable to read reset message.");
+    return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+  }
+  if (reset->tag() != kPRST) {
+    set_detailed_error("Incorrect message tag.");
+    return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+  }
+
+  if (reset->GetUint64(kRNON, &packet.nonce_proof) != QUIC_NO_ERROR) {
+    set_detailed_error("Unable to read nonce proof.");
+    return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+  }
+  // TODO(satyamshekhar): validate nonce to protect against DoS.
+
+  if (reset->GetUint64(kRSEQ, &packet.rejected_sequence_number) !=
+      QUIC_NO_ERROR) {
+    set_detailed_error("Unable to read rejected sequence number.");
+    return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+  }
+
+  StringPiece address;
+  if (reset->GetStringPiece(kCADR, &address)) {
+    QuicSocketAddressCoder address_coder;
+    if (address_coder.Decode(address.data(), address.length())) {
+      packet.client_address = IPEndPoint(address_coder.ip(),
+                                         address_coder.port());
+    }
+  }
+
+  visitor_->OnPublicResetPacket(packet);
+  return true;
+}
+
+bool QuicFramer::ProcessRevivedPacket(QuicPacketHeader* header,
+                                      StringPiece payload) {
+  DCHECK(!reader_.get());
+
+  visitor_->OnRevivedPacket();
+
+  header->entropy_hash = GetPacketEntropyHash(*header);
+
+  if (!visitor_->OnPacketHeader(*header)) {
+    return true;
+  }
+
+  if (payload.length() > kMaxPacketSize) {
+    set_detailed_error("Revived packet too large.");
+    return RaiseError(QUIC_PACKET_TOO_LARGE);
+  }
+
+  reader_.reset(new QuicDataReader(payload.data(), payload.length()));
+  if (!ProcessFrameData(*header)) {
+    DCHECK_NE(QUIC_NO_ERROR, error_);  // ProcessFrameData sets the error.
+    DLOG(WARNING) << "Unable to process frame data.";
+    return false;
+  }
+
+  visitor_->OnPacketComplete();
+  reader_.reset(nullptr);
+  return true;
+}
+
+bool QuicFramer::AppendPacketHeader(const QuicPacketHeader& header,
+                                    QuicDataWriter* writer) {
+  DVLOG(1) << "Appending header: " << header;
+  DCHECK(header.fec_group > 0 || header.is_in_fec_group == NOT_IN_FEC_GROUP);
+  uint8 public_flags = 0;
+  if (header.public_header.reset_flag) {
+    public_flags |= PACKET_PUBLIC_FLAGS_RST;
+  }
+  if (header.public_header.version_flag) {
+    public_flags |= PACKET_PUBLIC_FLAGS_VERSION;
+  }
+
+  public_flags |=
+      GetSequenceNumberFlags(header.public_header.sequence_number_length)
+          << kPublicHeaderSequenceNumberShift;
+
+  switch (header.public_header.connection_id_length) {
+    case PACKET_0BYTE_CONNECTION_ID:
+      if (!writer->WriteUInt8(
+              public_flags | PACKET_PUBLIC_FLAGS_0BYTE_CONNECTION_ID)) {
+        return false;
+      }
+      break;
+    case PACKET_1BYTE_CONNECTION_ID:
+      if (!writer->WriteUInt8(
+              public_flags | PACKET_PUBLIC_FLAGS_1BYTE_CONNECTION_ID)) {
+         return false;
+      }
+      if (!writer->WriteUInt8(
+              header.public_header.connection_id & k1ByteConnectionIdMask)) {
+        return false;
+      }
+      break;
+    case PACKET_4BYTE_CONNECTION_ID:
+      if (!writer->WriteUInt8(
+              public_flags | PACKET_PUBLIC_FLAGS_4BYTE_CONNECTION_ID)) {
+         return false;
+      }
+      if (!writer->WriteUInt32(
+              header.public_header.connection_id & k4ByteConnectionIdMask)) {
+        return false;
+      }
+      break;
+    case PACKET_8BYTE_CONNECTION_ID:
+      if (!writer->WriteUInt8(
+              public_flags | PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID)) {
+        return false;
+      }
+      if (!writer->WriteUInt64(header.public_header.connection_id)) {
+        return false;
+      }
+      break;
+  }
+  last_serialized_connection_id_ = header.public_header.connection_id;
+
+  if (header.public_header.version_flag) {
+    DCHECK(!is_server_);
+    writer->WriteUInt32(QuicVersionToQuicTag(quic_version_));
+  }
+
+  if (!AppendPacketSequenceNumber(header.public_header.sequence_number_length,
+                                  header.packet_sequence_number, writer)) {
+    return false;
+  }
+
+  uint8 private_flags = 0;
+  if (header.entropy_flag) {
+    private_flags |= PACKET_PRIVATE_FLAGS_ENTROPY;
+  }
+  if (header.is_in_fec_group == IN_FEC_GROUP) {
+    private_flags |= PACKET_PRIVATE_FLAGS_FEC_GROUP;
+  }
+  if (header.fec_flag) {
+    private_flags |= PACKET_PRIVATE_FLAGS_FEC;
+  }
+  if (!writer->WriteUInt8(private_flags)) {
+    return false;
+  }
+
+  // The FEC group number is the sequence number of the first fec
+  // protected packet, or 0 if this packet is not protected.
+  if (header.is_in_fec_group == IN_FEC_GROUP) {
+    DCHECK_GE(header.packet_sequence_number, header.fec_group);
+    DCHECK_GT(255u, header.packet_sequence_number - header.fec_group);
+    // Offset from the current packet sequence number to the first fec
+    // protected packet.
+    uint8 first_fec_protected_packet_offset =
+        header.packet_sequence_number - header.fec_group;
+    if (!writer->WriteBytes(&first_fec_protected_packet_offset, 1)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+const QuicTime::Delta QuicFramer::CalculateTimestampFromWire(
+    uint32 time_delta_us) {
+  // The new time_delta might have wrapped to the next epoch, or it
+  // might have reverse wrapped to the previous epoch, or it might
+  // remain in the same epoch. Select the time closest to the previous
+  // time.
+  //
+  // epoch_delta is the delta between epochs. A delta is 4 bytes of
+  // microseconds.
+  const uint64 epoch_delta = GG_UINT64_C(1) << 32;
+  uint64 epoch = last_timestamp_.ToMicroseconds() & ~(epoch_delta - 1);
+  // Wrapping is safe here because a wrapped value will not be ClosestTo below.
+  uint64 prev_epoch = epoch - epoch_delta;
+  uint64 next_epoch = epoch + epoch_delta;
+
+  uint64 time = ClosestTo(last_timestamp_.ToMicroseconds(),
+                          epoch + time_delta_us,
+                          ClosestTo(last_timestamp_.ToMicroseconds(),
+                                    prev_epoch + time_delta_us,
+                                    next_epoch + time_delta_us));
+
+  return QuicTime::Delta::FromMicroseconds(time);
+}
+
+QuicPacketSequenceNumber QuicFramer::CalculatePacketSequenceNumberFromWire(
+    QuicSequenceNumberLength sequence_number_length,
+    QuicPacketSequenceNumber packet_sequence_number) const {
+  // The new sequence number might have wrapped to the next epoch, or
+  // it might have reverse wrapped to the previous epoch, or it might
+  // remain in the same epoch.  Select the sequence number closest to the
+  // next expected sequence number, the previous sequence number plus 1.
+
+  // epoch_delta is the delta between epochs the sequence number was serialized
+  // with, so the correct value is likely the same epoch as the last sequence
+  // number or an adjacent epoch.
+  const QuicPacketSequenceNumber epoch_delta =
+      GG_UINT64_C(1) << (8 * sequence_number_length);
+  QuicPacketSequenceNumber next_sequence_number = last_sequence_number_ + 1;
+  QuicPacketSequenceNumber epoch = last_sequence_number_ & ~(epoch_delta - 1);
+  QuicPacketSequenceNumber prev_epoch = epoch - epoch_delta;
+  QuicPacketSequenceNumber next_epoch = epoch + epoch_delta;
+
+  return ClosestTo(next_sequence_number,
+                   epoch + packet_sequence_number,
+                   ClosestTo(next_sequence_number,
+                             prev_epoch + packet_sequence_number,
+                             next_epoch + packet_sequence_number));
+}
+
+bool QuicFramer::ProcessPublicHeader(
+    QuicPacketPublicHeader* public_header) {
+  uint8 public_flags;
+  if (!reader_->ReadBytes(&public_flags, 1)) {
+    set_detailed_error("Unable to read public flags.");
+    return false;
+  }
+
+  public_header->reset_flag = (public_flags & PACKET_PUBLIC_FLAGS_RST) != 0;
+  public_header->version_flag =
+      (public_flags & PACKET_PUBLIC_FLAGS_VERSION) != 0;
+
+  if (validate_flags_ &&
+      !public_header->version_flag && public_flags > PACKET_PUBLIC_FLAGS_MAX) {
+    set_detailed_error("Illegal public flags value.");
+    return false;
+  }
+
+  if (public_header->reset_flag && public_header->version_flag) {
+    set_detailed_error("Got version flag in reset packet");
+    return false;
+  }
+
+  switch (public_flags & PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID) {
+    case PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID:
+      if (!reader_->ReadUInt64(&public_header->connection_id)) {
+        set_detailed_error("Unable to read ConnectionId.");
+        return false;
+      }
+      public_header->connection_id_length = PACKET_8BYTE_CONNECTION_ID;
+      break;
+    case PACKET_PUBLIC_FLAGS_4BYTE_CONNECTION_ID:
+      // If the connection_id is truncated, expect to read the last serialized
+      // connection_id.
+      if (!reader_->ReadBytes(&public_header->connection_id,
+                              PACKET_4BYTE_CONNECTION_ID)) {
+        set_detailed_error("Unable to read ConnectionId.");
+        return false;
+      }
+      if ((public_header->connection_id & k4ByteConnectionIdMask) !=
+          (last_serialized_connection_id_ & k4ByteConnectionIdMask)) {
+        set_detailed_error("Truncated 4 byte ConnectionId does not match "
+                           "previous connection_id.");
+        return false;
+      }
+      public_header->connection_id_length = PACKET_4BYTE_CONNECTION_ID;
+      public_header->connection_id = last_serialized_connection_id_;
+      break;
+    case PACKET_PUBLIC_FLAGS_1BYTE_CONNECTION_ID:
+      if (!reader_->ReadBytes(&public_header->connection_id,
+                              PACKET_1BYTE_CONNECTION_ID)) {
+        set_detailed_error("Unable to read ConnectionId.");
+        return false;
+      }
+      if ((public_header->connection_id & k1ByteConnectionIdMask) !=
+          (last_serialized_connection_id_ & k1ByteConnectionIdMask)) {
+        set_detailed_error("Truncated 1 byte ConnectionId does not match "
+                           "previous connection_id.");
+        return false;
+      }
+      public_header->connection_id_length = PACKET_1BYTE_CONNECTION_ID;
+      public_header->connection_id = last_serialized_connection_id_;
+      break;
+    case PACKET_PUBLIC_FLAGS_0BYTE_CONNECTION_ID:
+      public_header->connection_id_length = PACKET_0BYTE_CONNECTION_ID;
+      public_header->connection_id = last_serialized_connection_id_;
+      break;
+  }
+
+  public_header->sequence_number_length =
+      ReadSequenceNumberLength(
+          public_flags >> kPublicHeaderSequenceNumberShift);
+
+  // Read the version only if the packet is from the client.
+  // version flag from the server means version negotiation packet.
+  if (public_header->version_flag && is_server_) {
+    QuicTag version_tag;
+    if (!reader_->ReadUInt32(&version_tag)) {
+      set_detailed_error("Unable to read protocol version.");
+      return false;
+    }
+
+    // If the version from the new packet is the same as the version of this
+    // framer, then the public flags should be set to something we understand.
+    // If not, this raises an error.
+    QuicVersion version = QuicTagToQuicVersion(version_tag);
+    if (version == quic_version_ && public_flags > PACKET_PUBLIC_FLAGS_MAX) {
+      set_detailed_error("Illegal public flags value.");
+      return false;
+    }
+    public_header->versions.push_back(version);
+  }
+  return true;
+}
+
+// static
+QuicSequenceNumberLength QuicFramer::GetMinSequenceNumberLength(
+    QuicPacketSequenceNumber sequence_number) {
+  if (sequence_number < 1 << (PACKET_1BYTE_SEQUENCE_NUMBER * 8)) {
+    return PACKET_1BYTE_SEQUENCE_NUMBER;
+  } else if (sequence_number < 1 << (PACKET_2BYTE_SEQUENCE_NUMBER * 8)) {
+    return PACKET_2BYTE_SEQUENCE_NUMBER;
+  } else if (sequence_number <
+             GG_UINT64_C(1) << (PACKET_4BYTE_SEQUENCE_NUMBER * 8)) {
+    return PACKET_4BYTE_SEQUENCE_NUMBER;
+  } else {
+    return PACKET_6BYTE_SEQUENCE_NUMBER;
+  }
+}
+
+// static
+uint8 QuicFramer::GetSequenceNumberFlags(
+    QuicSequenceNumberLength sequence_number_length) {
+  switch (sequence_number_length) {
+    case PACKET_1BYTE_SEQUENCE_NUMBER:
+      return PACKET_FLAGS_1BYTE_SEQUENCE;
+    case PACKET_2BYTE_SEQUENCE_NUMBER:
+      return PACKET_FLAGS_2BYTE_SEQUENCE;
+    case PACKET_4BYTE_SEQUENCE_NUMBER:
+      return PACKET_FLAGS_4BYTE_SEQUENCE;
+    case PACKET_6BYTE_SEQUENCE_NUMBER:
+      return PACKET_FLAGS_6BYTE_SEQUENCE;
+    default:
+      LOG(DFATAL) << "Unreachable case statement.";
+      return PACKET_FLAGS_6BYTE_SEQUENCE;
+  }
+}
+
+// static
+QuicFramer::AckFrameInfo QuicFramer::GetAckFrameInfo(
+    const QuicAckFrame& frame) {
+  AckFrameInfo ack_info;
+  if (!frame.missing_packets.empty()) {
+    DCHECK_GE(frame.largest_observed, *frame.missing_packets.rbegin());
+    size_t cur_range_length = 0;
+    SequenceNumberSet::const_iterator iter = frame.missing_packets.begin();
+    QuicPacketSequenceNumber last_missing = *iter;
+    ++iter;
+    for (; iter != frame.missing_packets.end(); ++iter) {
+      if (cur_range_length != numeric_limits<uint8>::max() &&
+          *iter == (last_missing + 1)) {
+        ++cur_range_length;
+      } else {
+        ack_info.nack_ranges[last_missing - cur_range_length] =
+            cur_range_length;
+        cur_range_length = 0;
+      }
+      ack_info.max_delta = max(ack_info.max_delta, *iter - last_missing);
+      last_missing = *iter;
+    }
+    // Include the last nack range.
+    ack_info.nack_ranges[last_missing - cur_range_length] = cur_range_length;
+    // Include the range to the largest observed.
+    ack_info.max_delta = max(ack_info.max_delta,
+                             frame.largest_observed - last_missing);
+  }
+  return ack_info;
+}
+
+bool QuicFramer::ProcessPacketHeader(
+    QuicPacketHeader* header,
+    const QuicEncryptedPacket& packet) {
+  if (!ProcessPacketSequenceNumber(header->public_header.sequence_number_length,
+                                   &header->packet_sequence_number)) {
+    set_detailed_error("Unable to read sequence number.");
+    return RaiseError(QUIC_INVALID_PACKET_HEADER);
+  }
+
+  if (header->packet_sequence_number == 0u) {
+    set_detailed_error("Packet sequence numbers cannot be 0.");
+    return RaiseError(QUIC_INVALID_PACKET_HEADER);
+  }
+
+  if (!visitor_->OnUnauthenticatedHeader(*header)) {
+    return false;
+  }
+
+  if (!DecryptPayload(*header, packet)) {
+    set_detailed_error("Unable to decrypt payload.");
+    return RaiseError(QUIC_DECRYPTION_FAILURE);
+  }
+
+  uint8 private_flags;
+  if (!reader_->ReadBytes(&private_flags, 1)) {
+    set_detailed_error("Unable to read private flags.");
+    return RaiseError(QUIC_INVALID_PACKET_HEADER);
+  }
+
+  if (private_flags > PACKET_PRIVATE_FLAGS_MAX) {
+    set_detailed_error("Illegal private flags value.");
+    return RaiseError(QUIC_INVALID_PACKET_HEADER);
+  }
+
+  header->entropy_flag = (private_flags & PACKET_PRIVATE_FLAGS_ENTROPY) != 0;
+  header->fec_flag = (private_flags & PACKET_PRIVATE_FLAGS_FEC) != 0;
+
+  if ((private_flags & PACKET_PRIVATE_FLAGS_FEC_GROUP) != 0) {
+    header->is_in_fec_group = IN_FEC_GROUP;
+    uint8 first_fec_protected_packet_offset;
+    if (!reader_->ReadBytes(&first_fec_protected_packet_offset, 1)) {
+      set_detailed_error("Unable to read first fec protected packet offset.");
+      return RaiseError(QUIC_INVALID_PACKET_HEADER);
+    }
+    if (first_fec_protected_packet_offset >= header->packet_sequence_number) {
+      set_detailed_error("First fec protected packet offset must be less "
+                         "than the sequence number.");
+      return RaiseError(QUIC_INVALID_PACKET_HEADER);
+    }
+    header->fec_group =
+        header->packet_sequence_number - first_fec_protected_packet_offset;
+  }
+
+  header->entropy_hash = GetPacketEntropyHash(*header);
+  // Set the last sequence number after we have decrypted the packet
+  // so we are confident is not attacker controlled.
+  last_sequence_number_ = header->packet_sequence_number;
+  return true;
+}
+
+bool QuicFramer::ProcessPacketSequenceNumber(
+    QuicSequenceNumberLength sequence_number_length,
+    QuicPacketSequenceNumber* sequence_number) {
+  QuicPacketSequenceNumber wire_sequence_number = 0u;
+  if (!reader_->ReadBytes(&wire_sequence_number, sequence_number_length)) {
+    return false;
+  }
+
+  // TODO(ianswett): Explore the usefulness of trying multiple sequence numbers
+  // in case the first guess is incorrect.
+  *sequence_number =
+      CalculatePacketSequenceNumberFromWire(sequence_number_length,
+                                            wire_sequence_number);
+  return true;
+}
+
+bool QuicFramer::ProcessFrameData(const QuicPacketHeader& header) {
+  if (reader_->IsDoneReading()) {
+    set_detailed_error("Packet has no frames.");
+    return RaiseError(QUIC_MISSING_PAYLOAD);
+  }
+  while (!reader_->IsDoneReading()) {
+    uint8 frame_type;
+    if (!reader_->ReadBytes(&frame_type, 1)) {
+      set_detailed_error("Unable to read frame type.");
+      return RaiseError(QUIC_INVALID_FRAME_DATA);
+    }
+
+    if (frame_type & kQuicFrameTypeSpecialMask) {
+      // Stream Frame
+      if (frame_type & kQuicFrameTypeStreamMask) {
+        QuicStreamFrame frame;
+        if (!ProcessStreamFrame(frame_type, &frame)) {
+          return RaiseError(QUIC_INVALID_STREAM_DATA);
+        }
+        if (!visitor_->OnStreamFrame(frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      // Ack Frame
+      if (frame_type & kQuicFrameTypeAckMask) {
+        QuicAckFrame frame;
+        if (!ProcessAckFrame(frame_type, &frame)) {
+          return RaiseError(QUIC_INVALID_ACK_DATA);
+        }
+        if (!visitor_->OnAckFrame(frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      // Congestion Feedback Frame
+      if (frame_type & kQuicFrameTypeCongestionFeedbackMask) {
+        if (quic_version_ > QUIC_VERSION_22) {
+          set_detailed_error("Congestion Feedback Frame has been deprecated.");
+          DLOG(WARNING) << "Congestion Feedback Frame has been deprecated.";
+        }
+        QuicCongestionFeedbackFrame frame;
+        if (!ProcessCongestionFeedbackFrame(&frame)) {
+          return RaiseError(QUIC_INVALID_CONGESTION_FEEDBACK_DATA);
+        }
+        if (!visitor_->OnCongestionFeedbackFrame(frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      // This was a special frame type that did not match any
+      // of the known ones. Error.
+      set_detailed_error("Illegal frame type.");
+      DLOG(WARNING) << "Illegal frame type: "
+                    << static_cast<int>(frame_type);
+      return RaiseError(QUIC_INVALID_FRAME_DATA);
+    }
+
+    switch (frame_type) {
+      case PADDING_FRAME:
+        // We're done with the packet.
+        return true;
+
+      case RST_STREAM_FRAME: {
+        QuicRstStreamFrame frame;
+        if (!ProcessRstStreamFrame(&frame)) {
+          return RaiseError(QUIC_INVALID_RST_STREAM_DATA);
+        }
+        if (!visitor_->OnRstStreamFrame(frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      case CONNECTION_CLOSE_FRAME: {
+        QuicConnectionCloseFrame frame;
+        if (!ProcessConnectionCloseFrame(&frame)) {
+          return RaiseError(QUIC_INVALID_CONNECTION_CLOSE_DATA);
+        }
+
+        if (!visitor_->OnConnectionCloseFrame(frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      case GOAWAY_FRAME: {
+        QuicGoAwayFrame goaway_frame;
+        if (!ProcessGoAwayFrame(&goaway_frame)) {
+          return RaiseError(QUIC_INVALID_GOAWAY_DATA);
+        }
+        if (!visitor_->OnGoAwayFrame(goaway_frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      case WINDOW_UPDATE_FRAME: {
+        QuicWindowUpdateFrame window_update_frame;
+        if (!ProcessWindowUpdateFrame(&window_update_frame)) {
+          return RaiseError(QUIC_INVALID_WINDOW_UPDATE_DATA);
+        }
+        if (!visitor_->OnWindowUpdateFrame(window_update_frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      case BLOCKED_FRAME: {
+        QuicBlockedFrame blocked_frame;
+        if (!ProcessBlockedFrame(&blocked_frame)) {
+          return RaiseError(QUIC_INVALID_BLOCKED_DATA);
+        }
+        if (!visitor_->OnBlockedFrame(blocked_frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      case STOP_WAITING_FRAME: {
+        QuicStopWaitingFrame stop_waiting_frame;
+        if (!ProcessStopWaitingFrame(header, &stop_waiting_frame)) {
+          return RaiseError(QUIC_INVALID_STOP_WAITING_DATA);
+        }
+        if (!visitor_->OnStopWaitingFrame(stop_waiting_frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+      case PING_FRAME: {
+        // Ping has no payload.
+        QuicPingFrame ping_frame;
+        if (!visitor_->OnPingFrame(ping_frame)) {
+          DVLOG(1) << "Visitor asked to stop further processing.";
+          // Returning true since there was no parsing error.
+          return true;
+        }
+        continue;
+      }
+
+      default:
+        set_detailed_error("Illegal frame type.");
+        DLOG(WARNING) << "Illegal frame type: "
+                      << static_cast<int>(frame_type);
+        return RaiseError(QUIC_INVALID_FRAME_DATA);
+    }
+  }
+
+  return true;
+}
+
+bool QuicFramer::ProcessStreamFrame(uint8 frame_type,
+                                    QuicStreamFrame* frame) {
+  uint8 stream_flags = frame_type;
+
+  stream_flags &= ~kQuicFrameTypeStreamMask;
+
+  // Read from right to left: StreamID, Offset, Data Length, Fin.
+  const uint8 stream_id_length = (stream_flags & kQuicStreamIDLengthMask) + 1;
+  stream_flags >>= kQuicStreamIdShift;
+
+  uint8 offset_length = (stream_flags & kQuicStreamOffsetMask);
+  // There is no encoding for 1 byte, only 0 and 2 through 8.
+  if (offset_length > 0) {
+    offset_length += 1;
+  }
+  stream_flags >>= kQuicStreamOffsetShift;
+
+  bool has_data_length =
+      (stream_flags & kQuicStreamDataLengthMask) == kQuicStreamDataLengthMask;
+  stream_flags >>= kQuicStreamDataLengthShift;
+
+  frame->fin = (stream_flags & kQuicStreamFinMask) == kQuicStreamFinShift;
+
+  frame->stream_id = 0;
+  if (!reader_->ReadBytes(&frame->stream_id, stream_id_length)) {
+    set_detailed_error("Unable to read stream_id.");
+    return false;
+  }
+
+  frame->offset = 0;
+  if (!reader_->ReadBytes(&frame->offset, offset_length)) {
+    set_detailed_error("Unable to read offset.");
+    return false;
+  }
+
+  StringPiece frame_data;
+  if (has_data_length) {
+    if (!reader_->ReadStringPiece16(&frame_data)) {
+      set_detailed_error("Unable to read frame data.");
+      return false;
+    }
+  } else {
+    if (!reader_->ReadStringPiece(&frame_data, reader_->BytesRemaining())) {
+      set_detailed_error("Unable to read frame data.");
+      return false;
+    }
+  }
+  // Point frame to the right data.
+  frame->data.Clear();
+  if (!frame_data.empty()) {
+    frame->data.Append(const_cast<char*>(frame_data.data()), frame_data.size());
+  }
+
+  return true;
+}
+
+bool QuicFramer::ProcessAckFrame(uint8 frame_type, QuicAckFrame* ack_frame) {
+  // Determine the three lengths from the frame type: largest observed length,
+  // missing sequence number length, and missing range length.
+  const QuicSequenceNumberLength missing_sequence_number_length =
+      ReadSequenceNumberLength(frame_type);
+  frame_type >>= kQuicSequenceNumberLengthShift;
+  const QuicSequenceNumberLength largest_observed_sequence_number_length =
+      ReadSequenceNumberLength(frame_type);
+  frame_type >>= kQuicSequenceNumberLengthShift;
+  ack_frame->is_truncated = frame_type & kQuicAckTruncatedMask;
+  frame_type >>= kQuicAckTruncatedShift;
+  bool has_nacks = frame_type & kQuicHasNacksMask;
+
+  if (!reader_->ReadBytes(&ack_frame->entropy_hash, 1)) {
+    set_detailed_error("Unable to read entropy hash for received packets.");
+    return false;
+  }
+
+  if (!reader_->ReadBytes(&ack_frame->largest_observed,
+                          largest_observed_sequence_number_length)) {
+    set_detailed_error("Unable to read largest observed.");
+    return false;
+  }
+
+  uint64 delta_time_largest_observed_us;
+  if (!reader_->ReadUFloat16(&delta_time_largest_observed_us)) {
+    set_detailed_error("Unable to read delta time largest observed.");
+    return false;
+  }
+
+  if (delta_time_largest_observed_us == kUFloat16MaxValue) {
+    ack_frame->delta_time_largest_observed = QuicTime::Delta::Infinite();
+  } else {
+    ack_frame->delta_time_largest_observed =
+        QuicTime::Delta::FromMicroseconds(delta_time_largest_observed_us);
+  }
+
+  if (!ProcessTimestampsInAckFrame(ack_frame)) {
+    return false;
+  }
+
+  if (!has_nacks) {
+    return true;
+  }
+
+  uint8 num_missing_ranges;
+  if (!reader_->ReadBytes(&num_missing_ranges, 1)) {
+    set_detailed_error("Unable to read num missing packet ranges.");
+    return false;
+  }
+
+  QuicPacketSequenceNumber last_sequence_number = ack_frame->largest_observed;
+  for (size_t i = 0; i < num_missing_ranges; ++i) {
+    QuicPacketSequenceNumber missing_delta = 0;
+    if (!reader_->ReadBytes(&missing_delta, missing_sequence_number_length)) {
+      set_detailed_error("Unable to read missing sequence number delta.");
+      return false;
+    }
+    last_sequence_number -= missing_delta;
+    QuicPacketSequenceNumber range_length = 0;
+    if (!reader_->ReadBytes(&range_length, PACKET_1BYTE_SEQUENCE_NUMBER)) {
+      set_detailed_error("Unable to read missing sequence number range.");
+      return false;
+    }
+    for (size_t i = 0; i <= range_length; ++i) {
+      ack_frame->missing_packets.insert(last_sequence_number - i);
+    }
+    // Subtract an extra 1 to ensure ranges are represented efficiently and
+    // can't overlap by 1 sequence number.  This allows a missing_delta of 0
+    // to represent an adjacent nack range.
+    last_sequence_number -= (range_length + 1);
+  }
+
+  // Parse the revived packets list.
+  uint8 num_revived_packets;
+  if (!reader_->ReadBytes(&num_revived_packets, 1)) {
+    set_detailed_error("Unable to read num revived packets.");
+    return false;
+  }
+
+  for (size_t i = 0; i < num_revived_packets; ++i) {
+    QuicPacketSequenceNumber revived_packet = 0;
+    if (!reader_->ReadBytes(&revived_packet,
+                            largest_observed_sequence_number_length)) {
+      set_detailed_error("Unable to read revived packet.");
+      return false;
+    }
+
+    ack_frame->revived_packets.insert(revived_packet);
+  }
+
+  return true;
+}
+
+bool QuicFramer::ProcessTimestampsInAckFrame(QuicAckFrame* ack_frame) {
+  if (version() > QUIC_VERSION_22 && !ack_frame->is_truncated) {
+    uint8 num_received_packets;
+    if (!reader_->ReadBytes(&num_received_packets, 1)) {
+      set_detailed_error("Unable to read num received packets.");
+      return false;
+    }
+
+    if (num_received_packets > 0) {
+      uint8 delta_from_largest_observed;
+      if (!reader_->ReadBytes(&delta_from_largest_observed,
+                              PACKET_1BYTE_SEQUENCE_NUMBER)) {
+        set_detailed_error(
+            "Unable to read sequence delta in received packets.");
+        return false;
+      }
+      QuicPacketSequenceNumber seq_num = ack_frame->largest_observed -
+          delta_from_largest_observed;
+
+      // Time delta from the framer creation.
+      uint32 time_delta_us;
+      if (!reader_->ReadBytes(&time_delta_us, sizeof(time_delta_us))) {
+        set_detailed_error("Unable to read time delta in received packets.");
+        return false;
+      }
+
+      last_timestamp_ = CalculateTimestampFromWire(time_delta_us);
+
+      ack_frame->received_packet_times.push_back(
+          make_pair(seq_num, creation_time_.Add(last_timestamp_)));
+
+      for (uint8 i = 1; i < num_received_packets; ++i) {
+        if (!reader_->ReadBytes(&delta_from_largest_observed,
+                                PACKET_1BYTE_SEQUENCE_NUMBER)) {
+          set_detailed_error(
+              "Unable to read sequence delta in received packets.");
+          return false;
+        }
+        seq_num = ack_frame->largest_observed - delta_from_largest_observed;
+
+        // Time delta from the previous timestamp.
+        uint64 incremental_time_delta_us;
+        if (!reader_->ReadUFloat16(&incremental_time_delta_us)) {
+          set_detailed_error(
+              "Unable to read incremental time delta in received packets.");
+          return false;
+        }
+
+        last_timestamp_ = last_timestamp_.Add(
+            QuicTime::Delta::FromMicroseconds(incremental_time_delta_us));
+        ack_frame->received_packet_times.push_back(
+            make_pair(seq_num, creation_time_.Add(last_timestamp_)));
+      }
+    }
+  }
+  return true;
+}
+
+bool QuicFramer::ProcessStopWaitingFrame(const QuicPacketHeader& header,
+                                         QuicStopWaitingFrame* stop_waiting) {
+  if (!reader_->ReadBytes(&stop_waiting->entropy_hash, 1)) {
+    set_detailed_error("Unable to read entropy hash for sent packets.");
+    return false;
+  }
+
+  QuicPacketSequenceNumber least_unacked_delta = 0;
+  if (!reader_->ReadBytes(&least_unacked_delta,
+                          header.public_header.sequence_number_length)) {
+    set_detailed_error("Unable to read least unacked delta.");
+    return false;
+  }
+  DCHECK_GE(header.packet_sequence_number, least_unacked_delta);
+  stop_waiting->least_unacked =
+      header.packet_sequence_number - least_unacked_delta;
+
+  return true;
+}
+
+bool QuicFramer::ProcessCongestionFeedbackFrame(
+    QuicCongestionFeedbackFrame* frame) {
+  uint8 feedback_type;
+  if (!reader_->ReadBytes(&feedback_type, 1)) {
+    set_detailed_error("Unable to read congestion feedback type.");
+    return false;
+  }
+  frame->type =
+      static_cast<CongestionFeedbackType>(feedback_type);
+
+  switch (frame->type) {
+    case kTCP: {
+      CongestionFeedbackMessageTCP* tcp = &frame->tcp;
+      uint16 receive_window = 0;
+      if (!reader_->ReadUInt16(&receive_window)) {
+        set_detailed_error("Unable to read receive window.");
+        return false;
+      }
+      // Simple bit packing, don't send the 4 least significant bits.
+      tcp->receive_window = static_cast<QuicByteCount>(receive_window) << 4;
+      break;
+    }
+    default:
+      set_detailed_error("Illegal congestion feedback type.");
+      DLOG(WARNING) << "Illegal congestion feedback type: "
+                    << frame->type;
+      return RaiseError(QUIC_INVALID_FRAME_DATA);
+  }
+
+  return true;
+}
+
+bool QuicFramer::ProcessRstStreamFrame(QuicRstStreamFrame* frame) {
+  if (!reader_->ReadUInt32(&frame->stream_id)) {
+    set_detailed_error("Unable to read stream_id.");
+    return false;
+  }
+
+  if (!reader_->ReadUInt64(&frame->byte_offset)) {
+    set_detailed_error("Unable to read rst stream sent byte offset.");
+    return false;
+  }
+
+  uint32 error_code;
+  if (!reader_->ReadUInt32(&error_code)) {
+    set_detailed_error("Unable to read rst stream error code.");
+    return false;
+  }
+
+  if (error_code >= QUIC_STREAM_LAST_ERROR ||
+      error_code < QUIC_STREAM_NO_ERROR) {
+    set_detailed_error("Invalid rst stream error code.");
+    return false;
+  }
+
+  frame->error_code = static_cast<QuicRstStreamErrorCode>(error_code);
+
+  StringPiece error_details;
+  if (!reader_->ReadStringPiece16(&error_details)) {
+    set_detailed_error("Unable to read rst stream error details.");
+    return false;
+  }
+  frame->error_details = error_details.as_string();
+
+  return true;
+}
+
+bool QuicFramer::ProcessConnectionCloseFrame(QuicConnectionCloseFrame* frame) {
+  uint32 error_code;
+  if (!reader_->ReadUInt32(&error_code)) {
+    set_detailed_error("Unable to read connection close error code.");
+    return false;
+  }
+
+  if (error_code >= QUIC_LAST_ERROR ||
+         error_code < QUIC_NO_ERROR) {
+    set_detailed_error("Invalid error code.");
+    return false;
+  }
+
+  frame->error_code = static_cast<QuicErrorCode>(error_code);
+
+  StringPiece error_details;
+  if (!reader_->ReadStringPiece16(&error_details)) {
+    set_detailed_error("Unable to read connection close error details.");
+    return false;
+  }
+  frame->error_details = error_details.as_string();
+
+  return true;
+}
+
+bool QuicFramer::ProcessGoAwayFrame(QuicGoAwayFrame* frame) {
+  uint32 error_code;
+  if (!reader_->ReadUInt32(&error_code)) {
+    set_detailed_error("Unable to read go away error code.");
+    return false;
+  }
+  frame->error_code = static_cast<QuicErrorCode>(error_code);
+
+  if (error_code >= QUIC_LAST_ERROR ||
+      error_code < QUIC_NO_ERROR) {
+    set_detailed_error("Invalid error code.");
+    return false;
+  }
+
+  uint32 stream_id;
+  if (!reader_->ReadUInt32(&stream_id)) {
+    set_detailed_error("Unable to read last good stream id.");
+    return false;
+  }
+  frame->last_good_stream_id = static_cast<QuicStreamId>(stream_id);
+
+  StringPiece reason_phrase;
+  if (!reader_->ReadStringPiece16(&reason_phrase)) {
+    set_detailed_error("Unable to read goaway reason.");
+    return false;
+  }
+  frame->reason_phrase = reason_phrase.as_string();
+
+  return true;
+}
+
+bool QuicFramer::ProcessWindowUpdateFrame(QuicWindowUpdateFrame* frame) {
+  if (!reader_->ReadUInt32(&frame->stream_id)) {
+    set_detailed_error("Unable to read stream_id.");
+    return false;
+  }
+
+  if (!reader_->ReadUInt64(&frame->byte_offset)) {
+    set_detailed_error("Unable to read window byte_offset.");
+    return false;
+  }
+
+  return true;
+}
+
+bool QuicFramer::ProcessBlockedFrame(QuicBlockedFrame* frame) {
+  if (!reader_->ReadUInt32(&frame->stream_id)) {
+    set_detailed_error("Unable to read stream_id.");
+    return false;
+  }
+
+  return true;
+}
+
+// static
+StringPiece QuicFramer::GetAssociatedDataFromEncryptedPacket(
+    const QuicEncryptedPacket& encrypted,
+    QuicConnectionIdLength connection_id_length,
+    bool includes_version,
+    QuicSequenceNumberLength sequence_number_length) {
+  return StringPiece(
+      encrypted.data() + kStartOfHashData, GetStartOfEncryptedData(
+          connection_id_length, includes_version, sequence_number_length)
+      - kStartOfHashData);
+}
+
+void QuicFramer::SetDecrypter(QuicDecrypter* decrypter,
+                              EncryptionLevel level) {
+  DCHECK(alternative_decrypter_.get() == nullptr);
+  DCHECK_GE(level, decrypter_level_);
+  decrypter_.reset(decrypter);
+  decrypter_level_ = level;
+}
+
+void QuicFramer::SetAlternativeDecrypter(QuicDecrypter* decrypter,
+                                         EncryptionLevel level,
+                                         bool latch_once_used) {
+  alternative_decrypter_.reset(decrypter);
+  alternative_decrypter_level_ = level;
+  alternative_decrypter_latch_ = latch_once_used;
+}
+
+const QuicDecrypter* QuicFramer::decrypter() const {
+  return decrypter_.get();
+}
+
+const QuicDecrypter* QuicFramer::alternative_decrypter() const {
+  return alternative_decrypter_.get();
+}
+
+void QuicFramer::SetEncrypter(EncryptionLevel level,
+                              QuicEncrypter* encrypter) {
+  DCHECK_GE(level, 0);
+  DCHECK_LT(level, NUM_ENCRYPTION_LEVELS);
+  encrypter_[level].reset(encrypter);
+}
+
+const QuicEncrypter* QuicFramer::encrypter(EncryptionLevel level) const {
+  DCHECK_GE(level, 0);
+  DCHECK_LT(level, NUM_ENCRYPTION_LEVELS);
+  DCHECK(encrypter_[level].get() != nullptr);
+  return encrypter_[level].get();
+}
+
+QuicEncryptedPacket* QuicFramer::EncryptPacket(
+    EncryptionLevel level,
+    QuicPacketSequenceNumber packet_sequence_number,
+    const QuicPacket& packet) {
+  DCHECK(encrypter_[level].get() != nullptr);
+
+  scoped_ptr<QuicData> out(encrypter_[level]->EncryptPacket(
+      packet_sequence_number, packet.AssociatedData(), packet.Plaintext()));
+  if (out.get() == nullptr) {
+    RaiseError(QUIC_ENCRYPTION_FAILURE);
+    return nullptr;
+  }
+  StringPiece header_data = packet.BeforePlaintext();
+  size_t len =  header_data.length() + out->length();
+  char* buffer = new char[len];
+  // TODO(rch): eliminate this buffer copy by passing in a buffer to Encrypt().
+  memcpy(buffer, header_data.data(), header_data.length());
+  memcpy(buffer + header_data.length(), out->data(), out->length());
+  return new QuicEncryptedPacket(buffer, len, true);
+}
+
+size_t QuicFramer::GetMaxPlaintextSize(size_t ciphertext_size) {
+  // In order to keep the code simple, we don't have the current encryption
+  // level to hand. Both the NullEncrypter and AES-GCM have a tag length of 12.
+  size_t min_plaintext_size = ciphertext_size;
+
+  for (int i = ENCRYPTION_NONE; i < NUM_ENCRYPTION_LEVELS; i++) {
+    if (encrypter_[i].get() != nullptr) {
+      size_t size = encrypter_[i]->GetMaxPlaintextSize(ciphertext_size);
+      if (size < min_plaintext_size) {
+        min_plaintext_size = size;
+      }
+    }
+  }
+
+  return min_plaintext_size;
+}
+
+bool QuicFramer::DecryptPayload(const QuicPacketHeader& header,
+                                const QuicEncryptedPacket& packet) {
+  StringPiece encrypted;
+  if (!reader_->ReadStringPiece(&encrypted, reader_->BytesRemaining())) {
+    return false;
+  }
+  DCHECK(decrypter_.get() != nullptr);
+  decrypted_.reset(decrypter_->DecryptPacket(
+      header.packet_sequence_number,
+      GetAssociatedDataFromEncryptedPacket(
+          packet,
+          header.public_header.connection_id_length,
+          header.public_header.version_flag,
+          header.public_header.sequence_number_length),
+      encrypted));
+  if (decrypted_.get() != nullptr) {
+    visitor_->OnDecryptedPacket(decrypter_level_);
+  } else if (alternative_decrypter_.get() != nullptr) {
+    decrypted_.reset(alternative_decrypter_->DecryptPacket(
+        header.packet_sequence_number,
+        GetAssociatedDataFromEncryptedPacket(
+            packet,
+            header.public_header.connection_id_length,
+            header.public_header.version_flag,
+            header.public_header.sequence_number_length),
+        encrypted));
+    if (decrypted_.get() != nullptr) {
+      visitor_->OnDecryptedPacket(alternative_decrypter_level_);
+      if (alternative_decrypter_latch_) {
+        // Switch to the alternative decrypter and latch so that we cannot
+        // switch back.
+        decrypter_.reset(alternative_decrypter_.release());
+        decrypter_level_ = alternative_decrypter_level_;
+        alternative_decrypter_level_ = ENCRYPTION_NONE;
+      } else {
+        // Switch the alternative decrypter so that we use it first next time.
+        decrypter_.swap(alternative_decrypter_);
+        EncryptionLevel level = alternative_decrypter_level_;
+        alternative_decrypter_level_ = decrypter_level_;
+        decrypter_level_ = level;
+      }
+    }
+  }
+
+  if (decrypted_.get() == nullptr) {
+    DLOG(WARNING) << "DecryptPacket failed for sequence_number:"
+                  << header.packet_sequence_number;
+    return false;
+  }
+
+  reader_.reset(new QuicDataReader(decrypted_->data(), decrypted_->length()));
+  return true;
+}
+
+size_t QuicFramer::GetAckFrameSize(
+    const QuicAckFrame& ack,
+    QuicSequenceNumberLength sequence_number_length) {
+  AckFrameInfo ack_info = GetAckFrameInfo(ack);
+  QuicSequenceNumberLength largest_observed_length =
+      GetMinSequenceNumberLength(ack.largest_observed);
+  QuicSequenceNumberLength missing_sequence_number_length =
+      GetMinSequenceNumberLength(ack_info.max_delta);
+
+  size_t ack_size = GetMinAckFrameSize(sequence_number_length,
+                                       largest_observed_length);
+  if (!ack_info.nack_ranges.empty()) {
+    ack_size += kNumberOfNackRangesSize  + kNumberOfRevivedPacketsSize;
+    ack_size += min(ack_info.nack_ranges.size(), kMaxNackRanges) *
+      (missing_sequence_number_length + PACKET_1BYTE_SEQUENCE_NUMBER);
+    ack_size += min(ack.revived_packets.size(),
+                    kMaxRevivedPackets) * largest_observed_length;
+  }
+
+  // In version 23, if the ack will be truncated due to too many nack ranges,
+  // then do not include the number of timestamps (1 byte).
+  if (version() > QUIC_VERSION_22 &&
+      ack_info.nack_ranges.size() <= kMaxNackRanges) {
+    // 1 byte for the number of timestamps.
+    ack_size += 1;
+    if (ack.received_packet_times.size() > 0) {
+      // 1 byte for sequence number, 4 bytes for timestamp for the first
+      // packet.
+      ack_size += 5;
+
+      // 1 byte for sequence number, 2 bytes for timestamp for the other
+      // packets.
+      ack_size += 3 * (ack.received_packet_times.size() - 1);
+    }
+  }
+
+  return ack_size;
+}
+
+size_t QuicFramer::ComputeFrameLength(
+    const QuicFrame& frame,
+    bool last_frame_in_packet,
+    InFecGroup is_in_fec_group,
+    QuicSequenceNumberLength sequence_number_length) {
+  switch (frame.type) {
+    case STREAM_FRAME:
+      return GetMinStreamFrameSize(frame.stream_frame->stream_id,
+                                   frame.stream_frame->offset,
+                                   last_frame_in_packet,
+                                   is_in_fec_group) +
+          frame.stream_frame->data.TotalBufferSize();
+    case ACK_FRAME: {
+      return GetAckFrameSize(*frame.ack_frame, sequence_number_length);
+    }
+    case CONGESTION_FEEDBACK_FRAME: {
+      size_t len = kQuicFrameTypeSize;
+      const QuicCongestionFeedbackFrame& congestion_feedback =
+          *frame.congestion_feedback_frame;
+      len += 1;  // Congestion feedback type.
+
+      switch (congestion_feedback.type) {
+        case kTCP:
+          len += 2;  // Receive window.
+          break;
+        default:
+          set_detailed_error("Illegal feedback type.");
+          DVLOG(1) << "Illegal feedback type: " << congestion_feedback.type;
+          break;
+      }
+      return len;
+    }
+    case STOP_WAITING_FRAME:
+      return GetStopWaitingFrameSize(sequence_number_length);
+    case PING_FRAME:
+      // Ping has no payload.
+      return kQuicFrameTypeSize;
+    case RST_STREAM_FRAME:
+      return GetMinRstStreamFrameSize() +
+          frame.rst_stream_frame->error_details.size();
+    case CONNECTION_CLOSE_FRAME:
+      return GetMinConnectionCloseFrameSize() +
+          frame.connection_close_frame->error_details.size();
+    case GOAWAY_FRAME:
+      return GetMinGoAwayFrameSize() + frame.goaway_frame->reason_phrase.size();
+    case WINDOW_UPDATE_FRAME:
+      return GetWindowUpdateFrameSize();
+    case BLOCKED_FRAME:
+      return GetBlockedFrameSize();
+    case PADDING_FRAME:
+      DCHECK(false);
+      return 0;
+    case NUM_FRAME_TYPES:
+      DCHECK(false);
+      return 0;
+  }
+
+  // Not reachable, but some Chrome compilers can't figure that out.  *sigh*
+  DCHECK(false);
+  return 0;
+}
+
+bool QuicFramer::AppendTypeByte(const QuicFrame& frame,
+                                bool no_stream_frame_length,
+                                QuicDataWriter* writer) {
+  uint8 type_byte = 0;
+  switch (frame.type) {
+    case STREAM_FRAME: {
+      if (frame.stream_frame == nullptr) {
+        LOG(DFATAL) << "Failed to append STREAM frame with no stream_frame.";
+      }
+      // Fin bit.
+      type_byte |= frame.stream_frame->fin ? kQuicStreamFinMask : 0;
+
+      // Data Length bit.
+      type_byte <<= kQuicStreamDataLengthShift;
+      type_byte |= no_stream_frame_length ? 0: kQuicStreamDataLengthMask;
+
+      // Offset 3 bits.
+      type_byte <<= kQuicStreamOffsetShift;
+      const size_t offset_len = GetStreamOffsetSize(frame.stream_frame->offset);
+      if (offset_len > 0) {
+        type_byte |= offset_len - 1;
+      }
+
+      // stream id 2 bits.
+      type_byte <<= kQuicStreamIdShift;
+      type_byte |= GetStreamIdSize(frame.stream_frame->stream_id) - 1;
+      type_byte |= kQuicFrameTypeStreamMask;  // Set Stream Frame Type to 1.
+      break;
+    }
+    case ACK_FRAME:
+      return true;
+    case CONGESTION_FEEDBACK_FRAME: {
+      // TODO(ianswett): Use extra 5 bits in the congestion feedback framing.
+      type_byte = kQuicFrameTypeCongestionFeedbackMask;
+      break;
+    }
+    default:
+      type_byte = frame.type;
+      break;
+  }
+
+  return writer->WriteUInt8(type_byte);
+}
+
+// static
+bool QuicFramer::AppendPacketSequenceNumber(
+    QuicSequenceNumberLength sequence_number_length,
+    QuicPacketSequenceNumber packet_sequence_number,
+    QuicDataWriter* writer) {
+  // Ensure the entire sequence number can be written.
+  if (writer->capacity() - writer->length() <
+      static_cast<size_t>(sequence_number_length)) {
+    return false;
+  }
+  switch (sequence_number_length) {
+    case PACKET_1BYTE_SEQUENCE_NUMBER:
+      return writer->WriteUInt8(
+          packet_sequence_number & k1ByteSequenceNumberMask);
+      break;
+    case PACKET_2BYTE_SEQUENCE_NUMBER:
+      return writer->WriteUInt16(
+          packet_sequence_number & k2ByteSequenceNumberMask);
+      break;
+    case PACKET_4BYTE_SEQUENCE_NUMBER:
+      return writer->WriteUInt32(
+          packet_sequence_number & k4ByteSequenceNumberMask);
+      break;
+    case PACKET_6BYTE_SEQUENCE_NUMBER:
+      return writer->WriteUInt48(
+          packet_sequence_number & k6ByteSequenceNumberMask);
+      break;
+    default:
+      DCHECK(false) << "sequence_number_length: " << sequence_number_length;
+      return false;
+  }
+}
+
+bool QuicFramer::AppendStreamFrame(
+    const QuicStreamFrame& frame,
+    bool no_stream_frame_length,
+    QuicDataWriter* writer) {
+  if (!writer->WriteBytes(&frame.stream_id, GetStreamIdSize(frame.stream_id))) {
+    LOG(DFATAL) << "Writing stream id size failed.";
+    return false;
+  }
+  if (!writer->WriteBytes(&frame.offset, GetStreamOffsetSize(frame.offset))) {
+    LOG(DFATAL) << "Writing offset size failed.";
+    return false;
+  }
+  if (!no_stream_frame_length) {
+    if (!writer->WriteUInt16(frame.data.TotalBufferSize())) {
+      LOG(DFATAL) << "Writing stream frame length failed";
+      return false;
+    }
+  }
+
+  if (!writer->WriteIOVector(frame.data)) {
+    LOG(DFATAL) << "Writing frame data failed.";
+    return false;
+  }
+  return true;
+}
+
+// static
+void QuicFramer::set_version(const QuicVersion version) {
+  DCHECK(IsSupportedVersion(version)) << QuicVersionToString(version);
+  quic_version_ = version;
+}
+
+bool QuicFramer::AppendAckFrameAndTypeByte(
+    const QuicPacketHeader& header,
+    const QuicAckFrame& frame,
+    QuicDataWriter* writer) {
+  AckFrameInfo ack_info = GetAckFrameInfo(frame);
+  QuicPacketSequenceNumber ack_largest_observed = frame.largest_observed;
+  QuicSequenceNumberLength largest_observed_length =
+      GetMinSequenceNumberLength(ack_largest_observed);
+  QuicSequenceNumberLength missing_sequence_number_length =
+      GetMinSequenceNumberLength(ack_info.max_delta);
+  // Determine whether we need to truncate ranges.
+  size_t available_range_bytes = writer->capacity() - writer->length() -
+      kNumberOfRevivedPacketsSize - kNumberOfNackRangesSize -
+      GetMinAckFrameSize(header.public_header.sequence_number_length,
+                         largest_observed_length);
+  size_t max_num_ranges = available_range_bytes /
+      (missing_sequence_number_length + PACKET_1BYTE_SEQUENCE_NUMBER);
+  max_num_ranges = min(kMaxNackRanges, max_num_ranges);
+  bool truncated = ack_info.nack_ranges.size() > max_num_ranges;
+  DVLOG_IF(1, truncated) << "Truncating ack from "
+                         << ack_info.nack_ranges.size() << " ranges to "
+                         << max_num_ranges;
+  // Write out the type byte by setting the low order bits and doing shifts
+  // to make room for the next bit flags to be set.
+  // Whether there are any nacks.
+  uint8 type_byte = ack_info.nack_ranges.empty() ? 0 : kQuicHasNacksMask;
+
+  // truncating bit.
+  type_byte <<= kQuicAckTruncatedShift;
+  type_byte |= truncated ? kQuicAckTruncatedMask : 0;
+
+  // Largest observed sequence number length.
+  type_byte <<= kQuicSequenceNumberLengthShift;
+  type_byte |= GetSequenceNumberFlags(largest_observed_length);
+
+  // Missing sequence number length.
+  type_byte <<= kQuicSequenceNumberLengthShift;
+  type_byte |= GetSequenceNumberFlags(missing_sequence_number_length);
+
+  type_byte |= kQuicFrameTypeAckMask;
+
+  if (!writer->WriteUInt8(type_byte)) {
+    return false;
+  }
+
+  QuicPacketEntropyHash ack_entropy_hash = frame.entropy_hash;
+  NackRangeMap::reverse_iterator ack_iter = ack_info.nack_ranges.rbegin();
+  if (truncated) {
+    // Skip the nack ranges which the truncated ack won't include and set
+    // a correct largest observed for the truncated ack.
+    for (size_t i = 1; i < (ack_info.nack_ranges.size() - max_num_ranges);
+         ++i) {
+      ++ack_iter;
+    }
+    // If the last range is followed by acks, include them.
+    // If the last range is followed by another range, specify the end of the
+    // range as the largest_observed.
+    ack_largest_observed = ack_iter->first - 1;
+    // Also update the entropy so it matches the largest observed.
+    ack_entropy_hash = entropy_calculator_->EntropyHash(ack_largest_observed);
+    ++ack_iter;
+  }
+
+  if (!writer->WriteUInt8(ack_entropy_hash)) {
+    return false;
+  }
+
+  if (!AppendPacketSequenceNumber(largest_observed_length,
+                                  ack_largest_observed, writer)) {
+    return false;
+  }
+
+  uint64 delta_time_largest_observed_us = kUFloat16MaxValue;
+  if (!frame.delta_time_largest_observed.IsInfinite()) {
+    DCHECK_LE(0u, frame.delta_time_largest_observed.ToMicroseconds());
+    delta_time_largest_observed_us =
+        frame.delta_time_largest_observed.ToMicroseconds();
+  }
+
+  if (!writer->WriteUFloat16(delta_time_largest_observed_us)) {
+    return false;
+  }
+
+  // Timestamp goes at the end of the required fields.
+  if (version() > QUIC_VERSION_22 && !truncated) {
+    if (!AppendTimestampToAckFrame(frame, writer)) {
+      return false;
+    }
+  }
+
+  if (ack_info.nack_ranges.empty()) {
+    return true;
+  }
+
+  const uint8 num_missing_ranges =
+      min(ack_info.nack_ranges.size(), max_num_ranges);
+  if (!writer->WriteBytes(&num_missing_ranges, 1)) {
+    return false;
+  }
+
+  int num_ranges_written = 0;
+  QuicPacketSequenceNumber last_sequence_written = ack_largest_observed;
+  for (; ack_iter != ack_info.nack_ranges.rend(); ++ack_iter) {
+    // Calculate the delta to the last number in the range.
+    QuicPacketSequenceNumber missing_delta =
+        last_sequence_written - (ack_iter->first + ack_iter->second);
+    if (!AppendPacketSequenceNumber(missing_sequence_number_length,
+                                    missing_delta, writer)) {
+      return false;
+    }
+    if (!AppendPacketSequenceNumber(PACKET_1BYTE_SEQUENCE_NUMBER,
+                                    ack_iter->second, writer)) {
+      return false;
+    }
+    // Subtract 1 so a missing_delta of 0 means an adjacent range.
+    last_sequence_written = ack_iter->first - 1;
+    ++num_ranges_written;
+  }
+  DCHECK_EQ(num_missing_ranges, num_ranges_written);
+
+  // Append revived packets.
+  // If not all the revived packets fit, only mention the ones that do.
+  uint8 num_revived_packets = min(frame.revived_packets.size(),
+                                  kMaxRevivedPackets);
+  num_revived_packets = min(
+      static_cast<size_t>(num_revived_packets),
+      (writer->capacity() - writer->length()) / largest_observed_length);
+  if (!writer->WriteBytes(&num_revived_packets, 1)) {
+    return false;
+  }
+
+  SequenceNumberSet::const_iterator iter = frame.revived_packets.begin();
+  for (int i = 0; i < num_revived_packets; ++i, ++iter) {
+    LOG_IF(DFATAL, !ContainsKey(frame.missing_packets, *iter));
+    if (!AppendPacketSequenceNumber(largest_observed_length,
+                                    *iter, writer)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool QuicFramer::AppendCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& frame,
+    QuicDataWriter* writer) {
+  if (!writer->WriteBytes(&frame.type, 1)) {
+    return false;
+  }
+
+  switch (frame.type) {
+    case kTCP: {
+      const CongestionFeedbackMessageTCP& tcp = frame.tcp;
+      DCHECK_LE(tcp.receive_window, 1u << 20);
+      // Simple bit packing, don't send the 4 least significant bits.
+      uint16 receive_window = static_cast<uint16>(tcp.receive_window >> 4);
+      if (!writer->WriteUInt16(receive_window)) {
+        return false;
+      }
+      break;
+    }
+    default:
+      return false;
+  }
+
+  return true;
+}
+
+bool QuicFramer::AppendTimestampToAckFrame(const QuicAckFrame& frame,
+                                           QuicDataWriter* writer) {
+  DCHECK_GE(version(), QUIC_VERSION_23);
+  DCHECK_GE(numeric_limits<uint8>::max(), frame.received_packet_times.size());
+  // num_received_packets is only 1 byte.
+  if (frame.received_packet_times.size() > numeric_limits<uint8>::max()) {
+    return false;
+  }
+
+  uint8 num_received_packets = frame.received_packet_times.size();
+
+  if (!writer->WriteBytes(&num_received_packets, 1)) {
+    return false;
+  }
+  if (num_received_packets == 0) {
+    return true;
+  }
+
+  PacketTimeList::const_iterator it = frame.received_packet_times.begin();
+  QuicPacketSequenceNumber sequence_number = it->first;
+  QuicPacketSequenceNumber delta_from_largest_observed =
+      frame.largest_observed - sequence_number;
+
+  DCHECK_GE(numeric_limits<uint8>::max(), delta_from_largest_observed);
+  if (delta_from_largest_observed > numeric_limits<uint8>::max()) {
+    return false;
+  }
+
+  if (!writer->WriteUInt8(
+    delta_from_largest_observed & k1ByteSequenceNumberMask)) {
+    return false;
+  }
+
+  // Use the lowest 4 bytes of the time delta from the creation_time_.
+  const uint64 time_epoch_delta_us = GG_UINT64_C(1) << 32;
+  uint32 time_delta_us =
+      static_cast<uint32>(it->second.Subtract(creation_time_).ToMicroseconds()
+                          & (time_epoch_delta_us - 1));
+  if (!writer->WriteBytes(&time_delta_us, sizeof(time_delta_us))) {
+    return false;
+  }
+
+  QuicTime prev_time = it->second;
+
+  for (++it; it != frame.received_packet_times.end(); ++it) {
+    sequence_number = it->first;
+    delta_from_largest_observed = frame.largest_observed - sequence_number;
+
+    if (delta_from_largest_observed > numeric_limits<uint8>::max()) {
+      return false;
+    }
+
+    if (!writer->WriteUInt8(
+            delta_from_largest_observed & k1ByteSequenceNumberMask)) {
+      return false;
+    }
+
+    uint64 time_delta_us = it->second.Subtract(prev_time).ToMicroseconds();
+    prev_time = it->second;
+    if (!writer->WriteUFloat16(time_delta_us)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool QuicFramer::AppendStopWaitingFrame(
+    const QuicPacketHeader& header,
+    const QuicStopWaitingFrame& frame,
+    QuicDataWriter* writer) {
+  DCHECK_GE(header.packet_sequence_number, frame.least_unacked);
+  const QuicPacketSequenceNumber least_unacked_delta =
+      header.packet_sequence_number - frame.least_unacked;
+  const QuicPacketSequenceNumber length_shift =
+      header.public_header.sequence_number_length * 8;
+  if (!writer->WriteUInt8(frame.entropy_hash)) {
+    LOG(DFATAL) << " hash failed";
+    return false;
+  }
+
+  if (least_unacked_delta >> length_shift > 0) {
+    LOG(DFATAL) << "sequence_number_length "
+                << header.public_header.sequence_number_length
+                << " is too small for least_unacked_delta: "
+                << least_unacked_delta;
+    return false;
+  }
+  if (!AppendPacketSequenceNumber(header.public_header.sequence_number_length,
+                                  least_unacked_delta, writer)) {
+    LOG(DFATAL) << " seq failed: "
+                << header.public_header.sequence_number_length;
+    return false;
+  }
+
+  return true;
+}
+
+bool QuicFramer::AppendRstStreamFrame(
+        const QuicRstStreamFrame& frame,
+        QuicDataWriter* writer) {
+  if (!writer->WriteUInt32(frame.stream_id)) {
+    return false;
+  }
+
+  if (!writer->WriteUInt64(frame.byte_offset)) {
+    return false;
+  }
+
+  uint32 error_code = static_cast<uint32>(frame.error_code);
+  if (!writer->WriteUInt32(error_code)) {
+    return false;
+  }
+
+  if (!writer->WriteStringPiece16(frame.error_details)) {
+    return false;
+  }
+  return true;
+}
+
+bool QuicFramer::AppendConnectionCloseFrame(
+    const QuicConnectionCloseFrame& frame,
+    QuicDataWriter* writer) {
+  uint32 error_code = static_cast<uint32>(frame.error_code);
+  if (!writer->WriteUInt32(error_code)) {
+    return false;
+  }
+  if (!writer->WriteStringPiece16(frame.error_details)) {
+    return false;
+  }
+  return true;
+}
+
+bool QuicFramer::AppendGoAwayFrame(const QuicGoAwayFrame& frame,
+                                   QuicDataWriter* writer) {
+  uint32 error_code = static_cast<uint32>(frame.error_code);
+  if (!writer->WriteUInt32(error_code)) {
+    return false;
+  }
+  uint32 stream_id = static_cast<uint32>(frame.last_good_stream_id);
+  if (!writer->WriteUInt32(stream_id)) {
+    return false;
+  }
+  if (!writer->WriteStringPiece16(frame.reason_phrase)) {
+    return false;
+  }
+  return true;
+}
+
+bool QuicFramer::AppendWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
+                                         QuicDataWriter* writer) {
+  uint32 stream_id = static_cast<uint32>(frame.stream_id);
+  if (!writer->WriteUInt32(stream_id)) {
+    return false;
+  }
+  if (!writer->WriteUInt64(frame.byte_offset)) {
+    return false;
+  }
+  return true;
+}
+
+bool QuicFramer::AppendBlockedFrame(const QuicBlockedFrame& frame,
+                                    QuicDataWriter* writer) {
+  uint32 stream_id = static_cast<uint32>(frame.stream_id);
+  if (!writer->WriteUInt32(stream_id)) {
+    return false;
+  }
+  return true;
+}
+
+bool QuicFramer::RaiseError(QuicErrorCode error) {
+  DVLOG(1) << "Error detail: " << detailed_error_;
+  set_error(error);
+  visitor_->OnError(this);
+  reader_.reset(nullptr);
+  return false;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_framer.h b/net/quic/quic_framer.h
new file mode 100644
index 0000000..4cc51f1
--- /dev/null
+++ b/net/quic/quic_framer.h
@@ -0,0 +1,548 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_FRAMER_H_
+#define NET_QUIC_QUIC_FRAMER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicFramerPeer;
+}  // namespace test
+
+class QuicDataReader;
+class QuicDataWriter;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicFramer;
+
+// Number of bytes reserved for the frame type preceding each frame.
+const size_t kQuicFrameTypeSize = 1;
+// Number of bytes reserved for error code.
+const size_t kQuicErrorCodeSize = 4;
+// Number of bytes reserved to denote the length of error details field.
+const size_t kQuicErrorDetailsLengthSize = 2;
+
+// Maximum number of bytes reserved for stream id.
+const size_t kQuicMaxStreamIdSize = 4;
+// Maximum number of bytes reserved for byte offset in stream frame.
+const size_t kQuicMaxStreamOffsetSize = 8;
+// Number of bytes reserved to store payload length in stream frame.
+const size_t kQuicStreamPayloadLengthSize = 2;
+
+// Size in bytes of the entropy hash sent in ack frames.
+const size_t kQuicEntropyHashSize = 1;
+// Size in bytes reserved for the delta time of the largest observed
+// sequence number in ack frames.
+const size_t kQuicDeltaTimeLargestObservedSize = 2;
+// Size in bytes reserved for the number of received packets with timestamps.
+const size_t kQuicNumTimestampsSize = 1;
+// Size in bytes reserved for the number of missing packets in ack frames.
+const size_t kNumberOfNackRangesSize = 1;
+// Maximum number of missing packet ranges that can fit within an ack frame.
+const size_t kMaxNackRanges =
+    (1 << (kNumberOfNackRangesSize * 8)) - 1;
+// Size in bytes reserved for the number of revived packets in ack frames.
+const size_t kNumberOfRevivedPacketsSize = 1;
+// Maximum number of revived packets that can fit within an ack frame.
+const size_t kMaxRevivedPackets =
+    (1 << (kNumberOfRevivedPacketsSize * 8)) - 1;
+
+// This class receives callbacks from the framer when packets
+// are processed.
+class NET_EXPORT_PRIVATE QuicFramerVisitorInterface {
+ public:
+  virtual ~QuicFramerVisitorInterface() {}
+
+  // Called if an error is detected in the QUIC protocol.
+  virtual void OnError(QuicFramer* framer) = 0;
+
+  // Called only when |is_server_| is true and the the framer gets a packet with
+  // version flag true and the version on the packet doesn't match
+  // |quic_version_|. The visitor should return true after it updates the
+  // version of the |framer_| to |received_version| or false to stop processing
+  // this packet.
+  virtual bool OnProtocolVersionMismatch(QuicVersion received_version) = 0;
+
+  // Called when a new packet has been received, before it
+  // has been validated or processed.
+  virtual void OnPacket() = 0;
+
+  // Called when a public reset packet has been parsed but has not yet
+  // been validated.
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) = 0;
+
+  // Called only when |is_server_| is false and a version negotiation packet has
+  // been parsed.
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) = 0;
+
+  // Called when a lost packet has been recovered via FEC,
+  // before it has been processed.
+  virtual void OnRevivedPacket() = 0;
+
+  // Called when the public header has been parsed, but has not been
+  // authenticated. If it returns false, framing for this packet will cease.
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) = 0;
+
+  // Called when the unauthenticated portion of the header has been parsed.
+  // If OnUnauthenticatedHeader returns false, framing for this packet will
+  // cease.
+  virtual bool OnUnauthenticatedHeader(const QuicPacketHeader& header) = 0;
+
+  // Called when a packet has been decrypted. |level| is the encryption level
+  // of the packet.
+  virtual void OnDecryptedPacket(EncryptionLevel level) = 0;
+
+  // Called when the complete header of a packet had been parsed.
+  // If OnPacketHeader returns false, framing for this packet will cease.
+  virtual bool OnPacketHeader(const QuicPacketHeader& header) = 0;
+
+  // Called when a data packet is parsed that is part of an FEC group.
+  // |payload| is the non-encrypted FEC protected payload of the packet.
+  virtual void OnFecProtectedPayload(base::StringPiece payload) = 0;
+
+  // Called when a StreamFrame has been parsed.
+  virtual bool OnStreamFrame(const QuicStreamFrame& frame) = 0;
+
+  // Called when a AckFrame has been parsed.  If OnAckFrame returns false,
+  // the framer will stop parsing the current packet.
+  virtual bool OnAckFrame(const QuicAckFrame& frame) = 0;
+
+  // Called when a CongestionFeedbackFrame has been parsed.
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) = 0;
+
+  // Called when a StopWaitingFrame has been parsed.
+  virtual bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) = 0;
+
+  // Called when a PingFrame has been parsed.
+  virtual bool OnPingFrame(const QuicPingFrame& frame) = 0;
+
+  // Called when a RstStreamFrame has been parsed.
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) = 0;
+
+  // Called when a ConnectionCloseFrame has been parsed.
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) = 0;
+
+  // Called when a GoAwayFrame has been parsed.
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) = 0;
+
+  // Called when a WindowUpdateFrame has been parsed.
+  virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) = 0;
+
+  // Called when a BlockedFrame has been parsed.
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) = 0;
+
+  // Called when FEC data has been parsed.
+  virtual void OnFecData(const QuicFecData& fec) = 0;
+
+  // Called when a packet has been completely processed.
+  virtual void OnPacketComplete() = 0;
+};
+
+class NET_EXPORT_PRIVATE QuicFecBuilderInterface {
+ public:
+  virtual ~QuicFecBuilderInterface() {}
+
+  // Called when a data packet is constructed that is part of an FEC group.
+  // |payload| is the non-encrypted FEC protected payload of the packet.
+  virtual void OnBuiltFecProtectedPayload(const QuicPacketHeader& header,
+                                          base::StringPiece payload) = 0;
+};
+
+// This class calculates the received entropy of the ack packet being
+// framed, should it get truncated.
+class NET_EXPORT_PRIVATE QuicReceivedEntropyHashCalculatorInterface {
+ public:
+  virtual ~QuicReceivedEntropyHashCalculatorInterface() {}
+
+  // When an ack frame gets truncated while being framed the received
+  // entropy of the ack frame needs to be calculated since the some of the
+  // missing packets are not added and the largest observed might be lowered.
+  // This should return the received entropy hash of the packets received up to
+  // and including |sequence_number|.
+  virtual QuicPacketEntropyHash EntropyHash(
+      QuicPacketSequenceNumber sequence_number) const = 0;
+};
+
+// Class for parsing and constructing QUIC packets.  It has a
+// QuicFramerVisitorInterface that is called when packets are parsed.
+// It also has a QuicFecBuilder that is called when packets are constructed
+// in order to generate FEC data for subsequently building FEC packets.
+class NET_EXPORT_PRIVATE QuicFramer {
+ public:
+  // Constructs a new framer that installs a kNULL QuicEncrypter and
+  // QuicDecrypter for level ENCRYPTION_NONE. |supported_versions| specifies the
+  // list of supported QUIC versions. |quic_version_| is set to the maximum
+  // version in |supported_versions|.
+  QuicFramer(const QuicVersionVector& supported_versions,
+             QuicTime creation_time,
+             bool is_server);
+
+  virtual ~QuicFramer();
+
+  // Returns true if |version| is a supported protocol version.
+  bool IsSupportedVersion(const QuicVersion version) const;
+
+  // Set callbacks to be called from the framer.  A visitor must be set, or
+  // else the framer will likely crash.  It is acceptable for the visitor
+  // to do nothing.  If this is called multiple times, only the last visitor
+  // will be used.
+  void set_visitor(QuicFramerVisitorInterface* visitor) {
+    visitor_ = visitor;
+  }
+
+  // Set a builder to be called from the framer when building FEC protected
+  // packets.  If this is called multiple times, only the last builder
+  // will be used.  The builder need not be set.
+  void set_fec_builder(QuicFecBuilderInterface* builder) {
+    fec_builder_ = builder;
+  }
+
+  const QuicVersionVector& supported_versions() const {
+    return supported_versions_;
+  }
+
+  QuicVersion version() const {
+    return quic_version_;
+  }
+
+  void set_version(const QuicVersion version);
+
+  // Does not DCHECK for supported version. Used by tests to set unsupported
+  // version to trigger version negotiation.
+  void set_version_for_tests(const QuicVersion version) {
+    quic_version_ = version;
+  }
+
+  // Set entropy calculator to be called from the framer when it needs the
+  // entropy of a truncated ack frame. An entropy calculator must be set or else
+  // the framer will likely crash. If this is called multiple times, only the
+  // last calculator will be used.
+  void set_received_entropy_calculator(
+      QuicReceivedEntropyHashCalculatorInterface* entropy_calculator) {
+    entropy_calculator_ = entropy_calculator;
+  }
+
+  QuicErrorCode error() const {
+    return error_;
+  }
+
+  // Pass a UDP packet into the framer for parsing.
+  // Return true if the packet was processed succesfully. |packet| must be a
+  // single, complete UDP packet (not a frame of a packet).  This packet
+  // might be null padded past the end of the payload, which will be correctly
+  // ignored.
+  bool ProcessPacket(const QuicEncryptedPacket& packet);
+
+  // Pass a data packet that was revived from FEC data into the framer
+  // for parsing.
+  // Return true if the packet was processed succesfully. |payload| must be
+  // the complete DECRYPTED payload of the revived packet.
+  bool ProcessRevivedPacket(QuicPacketHeader* header,
+                            base::StringPiece payload);
+
+  // Largest size in bytes of all stream frame fields without the payload.
+  static size_t GetMinStreamFrameSize(QuicStreamId stream_id,
+                                      QuicStreamOffset offset,
+                                      bool last_frame_in_packet,
+                                      InFecGroup is_in_fec_group);
+  // Size in bytes of all ack frame fields without the missing packets.
+  static size_t GetMinAckFrameSize(
+      QuicSequenceNumberLength sequence_number_length,
+      QuicSequenceNumberLength largest_observed_length);
+  // Size in bytes of a stop waiting frame.
+  static size_t GetStopWaitingFrameSize(
+      QuicSequenceNumberLength sequence_number_length);
+  // Size in bytes of all reset stream frame without the error details.
+  static size_t GetMinRstStreamFrameSize();
+  // Size in bytes of all connection close frame fields without the error
+  // details and the missing packets from the enclosed ack frame.
+  static size_t GetMinConnectionCloseFrameSize();
+  // Size in bytes of all GoAway frame fields without the reason phrase.
+  static size_t GetMinGoAwayFrameSize();
+  // Size in bytes of all WindowUpdate frame fields.
+  static size_t GetWindowUpdateFrameSize();
+  // Size in bytes of all Blocked frame fields.
+  static size_t GetBlockedFrameSize();
+  // Size in bytes required to serialize the stream id.
+  static size_t GetStreamIdSize(QuicStreamId stream_id);
+  // Size in bytes required to serialize the stream offset.
+  static size_t GetStreamOffsetSize(QuicStreamOffset offset);
+  // Size in bytes required for a serialized version negotiation packet
+  static size_t GetVersionNegotiationPacketSize(size_t number_versions);
+
+  // Returns the number of bytes added to the packet for the specified frame,
+  // and 0 if the frame doesn't fit.  Includes the header size for the first
+  // frame.
+  size_t GetSerializedFrameLength(
+      const QuicFrame& frame,
+      size_t free_bytes,
+      bool first_frame_in_packet,
+      bool last_frame_in_packet,
+      InFecGroup is_in_fec_group,
+      QuicSequenceNumberLength sequence_number_length);
+
+  // Returns the associated data from the encrypted packet |encrypted| as a
+  // stringpiece.
+  static base::StringPiece GetAssociatedDataFromEncryptedPacket(
+      const QuicEncryptedPacket& encrypted,
+      QuicConnectionIdLength connection_id_length,
+      bool includes_version,
+      QuicSequenceNumberLength sequence_number_length);
+
+  // Returns a SerializedPacket whose |packet| member is owned by the caller,
+  // is created from the first |num_frames| frames, or is nullptr if the packet
+  // could not be created.  The packet must be of size |packet_size|.
+  SerializedPacket BuildDataPacket(const QuicPacketHeader& header,
+                                   const QuicFrames& frames,
+                                   size_t packet_size);
+
+  // Returns a SerializedPacket whose |packet| member is owned by the caller,
+  // and is populated with the fields in |header| and |fec|, or is nullptr if
+  // the packet could not be created.
+  SerializedPacket BuildFecPacket(const QuicPacketHeader& header,
+                                  const QuicFecData& fec);
+
+  // Returns a new public reset packet, owned by the caller.
+  static QuicEncryptedPacket* BuildPublicResetPacket(
+      const QuicPublicResetPacket& packet);
+
+  QuicEncryptedPacket* BuildVersionNegotiationPacket(
+      const QuicPacketPublicHeader& header,
+      const QuicVersionVector& supported_versions);
+
+  // SetDecrypter sets the primary decrypter, replacing any that already exists,
+  // and takes ownership. If an alternative decrypter is in place then the
+  // function DCHECKs. This is intended for cases where one knows that future
+  // packets will be using the new decrypter and the previous decrypter is now
+  // obsolete. |level| indicates the encryption level of the new decrypter.
+  void SetDecrypter(QuicDecrypter* decrypter, EncryptionLevel level);
+
+  // SetAlternativeDecrypter sets a decrypter that may be used to decrypt
+  // future packets and takes ownership of it. |level| indicates the encryption
+  // level of the decrypter. If |latch_once_used| is true, then the first time
+  // that the decrypter is successful it will replace the primary decrypter.
+  // Otherwise both decrypters will remain active and the primary decrypter
+  // will be the one last used.
+  void SetAlternativeDecrypter(QuicDecrypter* decrypter,
+                               EncryptionLevel level,
+                               bool latch_once_used);
+
+  const QuicDecrypter* decrypter() const;
+  const QuicDecrypter* alternative_decrypter() const;
+
+  // Changes the encrypter used for level |level| to |encrypter|. The function
+  // takes ownership of |encrypter|.
+  void SetEncrypter(EncryptionLevel level, QuicEncrypter* encrypter);
+  const QuicEncrypter* encrypter(EncryptionLevel level) const;
+
+  // Returns a new encrypted packet, owned by the caller.
+  QuicEncryptedPacket* EncryptPacket(EncryptionLevel level,
+                                     QuicPacketSequenceNumber sequence_number,
+                                     const QuicPacket& packet);
+
+  // Returns the maximum length of plaintext that can be encrypted
+  // to ciphertext no larger than |ciphertext_size|.
+  size_t GetMaxPlaintextSize(size_t ciphertext_size);
+
+  const std::string& detailed_error() { return detailed_error_; }
+
+  // The minimum sequence number length required to represent |sequence_number|.
+  static QuicSequenceNumberLength GetMinSequenceNumberLength(
+      QuicPacketSequenceNumber sequence_number);
+
+  void SetSupportedVersions(const QuicVersionVector& versions) {
+    supported_versions_ = versions;
+    quic_version_ = versions[0];
+  }
+
+  void set_validate_flags(bool value) { validate_flags_ = value; }
+
+  bool is_server() const { return is_server_; }
+
+ private:
+  friend class test::QuicFramerPeer;
+
+  typedef std::map<QuicPacketSequenceNumber, uint8> NackRangeMap;
+
+  struct AckFrameInfo {
+    AckFrameInfo();
+    ~AckFrameInfo();
+
+    // The maximum delta between ranges.
+    QuicPacketSequenceNumber max_delta;
+    // Nack ranges starting with start sequence numbers and lengths.
+    NackRangeMap nack_ranges;
+  };
+
+  QuicPacketEntropyHash GetPacketEntropyHash(
+      const QuicPacketHeader& header) const;
+
+  bool ProcessDataPacket(const QuicPacketPublicHeader& public_header,
+                         const QuicEncryptedPacket& packet);
+
+  bool ProcessPublicResetPacket(const QuicPacketPublicHeader& public_header);
+
+  bool ProcessVersionNegotiationPacket(QuicPacketPublicHeader* public_header);
+
+  bool ProcessPublicHeader(QuicPacketPublicHeader* header);
+
+  bool ProcessPacketHeader(QuicPacketHeader* header,
+                           const QuicEncryptedPacket& packet);
+
+  bool ProcessPacketSequenceNumber(
+      QuicSequenceNumberLength sequence_number_length,
+      QuicPacketSequenceNumber* sequence_number);
+  bool ProcessFrameData(const QuicPacketHeader& header);
+  bool ProcessStreamFrame(uint8 frame_type, QuicStreamFrame* frame);
+  bool ProcessAckFrame(uint8 frame_type, QuicAckFrame* frame);
+  bool ProcessTimestampsInAckFrame(QuicAckFrame* frame);
+  bool ProcessStopWaitingFrame(const QuicPacketHeader& public_header,
+                               QuicStopWaitingFrame* stop_waiting);
+  bool ProcessCongestionFeedbackFrame(
+      QuicCongestionFeedbackFrame* congestion_feedback);
+  bool ProcessRstStreamFrame(QuicRstStreamFrame* frame);
+  bool ProcessConnectionCloseFrame(QuicConnectionCloseFrame* frame);
+  bool ProcessGoAwayFrame(QuicGoAwayFrame* frame);
+  bool ProcessWindowUpdateFrame(QuicWindowUpdateFrame* frame);
+  bool ProcessBlockedFrame(QuicBlockedFrame* frame);
+
+  bool DecryptPayload(const QuicPacketHeader& header,
+                      const QuicEncryptedPacket& packet);
+
+  // Returns the full packet sequence number from the truncated
+  // wire format version and the last seen packet sequence number.
+  QuicPacketSequenceNumber CalculatePacketSequenceNumberFromWire(
+      QuicSequenceNumberLength sequence_number_length,
+      QuicPacketSequenceNumber packet_sequence_number) const;
+
+  // Returns the QuicTime::Delta corresponding to the time from when the framer
+  // was created.
+  const QuicTime::Delta CalculateTimestampFromWire(uint32 time_delta_us);
+
+  // Computes the wire size in bytes of the |ack| frame, assuming no truncation.
+  size_t GetAckFrameSize(const QuicAckFrame& ack,
+                         QuicSequenceNumberLength sequence_number_length);
+
+  // Computes the wire size in bytes of the payload of |frame|.
+  size_t ComputeFrameLength(const QuicFrame& frame,
+                            bool last_frame_in_packet,
+                            InFecGroup is_in_fec_group,
+                            QuicSequenceNumberLength sequence_number_length);
+
+  static bool AppendPacketSequenceNumber(
+      QuicSequenceNumberLength sequence_number_length,
+      QuicPacketSequenceNumber packet_sequence_number,
+      QuicDataWriter* writer);
+
+  static uint8 GetSequenceNumberFlags(
+      QuicSequenceNumberLength sequence_number_length);
+
+  static AckFrameInfo GetAckFrameInfo(const QuicAckFrame& frame);
+
+  // The Append* methods attempt to write the provided header or frame using the
+  // |writer|, and return true if successful.
+  bool AppendPacketHeader(const QuicPacketHeader& header,
+                          QuicDataWriter* writer);
+  bool AppendTypeByte(const QuicFrame& frame,
+                      bool last_frame_in_packet,
+                      QuicDataWriter* writer);
+  bool AppendStreamFrame(const QuicStreamFrame& frame,
+                         bool last_frame_in_packet,
+                         QuicDataWriter* builder);
+  bool AppendAckFrameAndTypeByte(const QuicPacketHeader& header,
+                                 const QuicAckFrame& frame,
+                                 QuicDataWriter* builder);
+  bool AppendCongestionFeedbackFrame(const QuicCongestionFeedbackFrame& frame,
+                                     QuicDataWriter* builder);
+  bool AppendTimestampToAckFrame(const QuicAckFrame& frame,
+                                 QuicDataWriter* builder);
+  bool AppendStopWaitingFrame(const QuicPacketHeader& header,
+                              const QuicStopWaitingFrame& frame,
+                              QuicDataWriter* builder);
+  bool AppendRstStreamFrame(const QuicRstStreamFrame& frame,
+                            QuicDataWriter* builder);
+  bool AppendConnectionCloseFrame(const QuicConnectionCloseFrame& frame,
+                                  QuicDataWriter* builder);
+  bool AppendGoAwayFrame(const QuicGoAwayFrame& frame, QuicDataWriter* writer);
+  bool AppendWindowUpdateFrame(const QuicWindowUpdateFrame& frame,
+                               QuicDataWriter* writer);
+  bool AppendBlockedFrame(const QuicBlockedFrame& frame,
+                          QuicDataWriter* writer);
+
+  bool RaiseError(QuicErrorCode error);
+
+  void set_error(QuicErrorCode error) {
+    error_ = error;
+  }
+
+  void set_detailed_error(const char* error) {
+    detailed_error_ = error;
+  }
+
+  std::string detailed_error_;
+  scoped_ptr<QuicDataReader> reader_;
+  QuicFramerVisitorInterface* visitor_;
+  QuicFecBuilderInterface* fec_builder_;
+  QuicReceivedEntropyHashCalculatorInterface* entropy_calculator_;
+  QuicErrorCode error_;
+  // Updated by ProcessPacketHeader when it succeeds.
+  QuicPacketSequenceNumber last_sequence_number_;
+  // Updated by WritePacketHeader.
+  QuicConnectionId last_serialized_connection_id_;
+  // Buffer containing decrypted payload data during parsing.
+  scoped_ptr<QuicData> decrypted_;
+  // Version of the protocol being used.
+  QuicVersion quic_version_;
+  // This vector contains QUIC versions which we currently support.
+  // This should be ordered such that the highest supported version is the first
+  // element, with subsequent elements in descending order (versions can be
+  // skipped as necessary).
+  QuicVersionVector supported_versions_;
+  // Primary decrypter used to decrypt packets during parsing.
+  scoped_ptr<QuicDecrypter> decrypter_;
+  // Alternative decrypter that can also be used to decrypt packets.
+  scoped_ptr<QuicDecrypter> alternative_decrypter_;
+  // The encryption level of |decrypter_|.
+  EncryptionLevel decrypter_level_;
+  // The encryption level of |alternative_decrypter_|.
+  EncryptionLevel alternative_decrypter_level_;
+  // |alternative_decrypter_latch_| is true if, when |alternative_decrypter_|
+  // successfully decrypts a packet, we should install it as the only
+  // decrypter.
+  bool alternative_decrypter_latch_;
+  // Encrypters used to encrypt packets via EncryptPacket().
+  scoped_ptr<QuicEncrypter> encrypter_[NUM_ENCRYPTION_LEVELS];
+  // Tracks if the framer is being used by the entity that received the
+  // connection or the entity that initiated it.
+  bool is_server_;
+  // If false, skip validation that the public flags are set to legal values.
+  bool validate_flags_;
+  // The time this framer was created.  Time written to the wire will be
+  // written as a delta from this value.
+  QuicTime creation_time_;
+  // The time delta computed for the last timestamp frame. This is relative to
+  // the creation_time.
+  QuicTime::Delta last_timestamp_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicFramer);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_FRAMER_H_
diff --git a/net/quic/quic_framer_test.cc b/net/quic/quic_framer_test.cc
new file mode 100644
index 0000000..535d31a
--- /dev/null
+++ b/net/quic/quic_framer_test.cc
@@ -0,0 +1,5074 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_framer.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/port.h"
+#include "base/stl_util.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/gtest_util.h"
+
+using base::hash_set;
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::numeric_limits;
+using std::pair;
+using std::string;
+using std::vector;
+using testing::Return;
+using testing::_;
+
+namespace net {
+namespace test {
+
+const QuicPacketSequenceNumber kEpoch = GG_UINT64_C(1) << 48;
+const QuicPacketSequenceNumber kMask = kEpoch - 1;
+
+// Index into the connection_id offset in the header.
+const size_t kConnectionIdOffset = kPublicFlagsSize;
+// Index into the version string in the header. (if present).
+const size_t kVersionOffset = kConnectionIdOffset + PACKET_8BYTE_CONNECTION_ID;
+
+// Size in bytes of the stream frame fields for an arbitrary StreamID and
+// offset and the last frame in a packet.
+size_t GetMinStreamFrameSize() {
+  return kQuicFrameTypeSize + kQuicMaxStreamIdSize + kQuicMaxStreamOffsetSize;
+}
+
+// Index into the sequence number offset in the header.
+size_t GetSequenceNumberOffset(QuicConnectionIdLength connection_id_length,
+                               bool include_version) {
+  return kConnectionIdOffset + connection_id_length +
+      (include_version ? kQuicVersionSize : 0);
+}
+
+size_t GetSequenceNumberOffset(bool include_version) {
+  return GetSequenceNumberOffset(PACKET_8BYTE_CONNECTION_ID, include_version);
+}
+
+// Index into the private flags offset in the data packet header.
+size_t GetPrivateFlagsOffset(QuicConnectionIdLength connection_id_length,
+                             bool include_version) {
+  return GetSequenceNumberOffset(connection_id_length, include_version) +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+}
+
+size_t GetPrivateFlagsOffset(bool include_version) {
+  return GetPrivateFlagsOffset(PACKET_8BYTE_CONNECTION_ID, include_version);
+}
+
+size_t GetPrivateFlagsOffset(bool include_version,
+                             QuicSequenceNumberLength sequence_number_length) {
+  return GetSequenceNumberOffset(PACKET_8BYTE_CONNECTION_ID, include_version) +
+      sequence_number_length;
+}
+
+// Index into the fec group offset in the header.
+size_t GetFecGroupOffset(QuicConnectionIdLength connection_id_length,
+                         bool include_version) {
+  return GetPrivateFlagsOffset(connection_id_length, include_version) +
+      kPrivateFlagsSize;
+}
+
+size_t GetFecGroupOffset(bool include_version) {
+  return GetPrivateFlagsOffset(PACKET_8BYTE_CONNECTION_ID, include_version) +
+      kPrivateFlagsSize;
+}
+
+size_t GetFecGroupOffset(bool include_version,
+                         QuicSequenceNumberLength sequence_number_length) {
+  return GetPrivateFlagsOffset(include_version, sequence_number_length) +
+      kPrivateFlagsSize;
+}
+
+// Index into the message tag of the public reset packet.
+// Public resets always have full connection_ids.
+const size_t kPublicResetPacketMessageTagOffset =
+    kConnectionIdOffset + PACKET_8BYTE_CONNECTION_ID;
+
+class TestEncrypter : public QuicEncrypter {
+ public:
+  virtual ~TestEncrypter() {}
+  virtual bool SetKey(StringPiece key) OVERRIDE {
+    return true;
+  }
+  virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+    return true;
+  }
+  virtual bool Encrypt(StringPiece nonce,
+                       StringPiece associated_data,
+                       StringPiece plaintext,
+                       unsigned char* output) OVERRIDE {
+    CHECK(false) << "Not implemented";
+    return false;
+  }
+  virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  StringPiece associated_data,
+                                  StringPiece plaintext) OVERRIDE {
+    sequence_number_ = sequence_number;
+    associated_data_ = associated_data.as_string();
+    plaintext_ = plaintext.as_string();
+    return new QuicData(plaintext.data(), plaintext.length());
+  }
+  virtual size_t GetKeySize() const OVERRIDE {
+    return 0;
+  }
+  virtual size_t GetNoncePrefixSize() const OVERRIDE {
+    return 0;
+  }
+  virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE {
+    return ciphertext_size;
+  }
+  virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE {
+    return plaintext_size;
+  }
+  virtual StringPiece GetKey() const OVERRIDE {
+    return StringPiece();
+  }
+  virtual StringPiece GetNoncePrefix() const OVERRIDE {
+    return StringPiece();
+  }
+  QuicPacketSequenceNumber sequence_number_;
+  string associated_data_;
+  string plaintext_;
+};
+
+class TestDecrypter : public QuicDecrypter {
+ public:
+  virtual ~TestDecrypter() {}
+  virtual bool SetKey(StringPiece key) OVERRIDE {
+    return true;
+  }
+  virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+    return true;
+  }
+  virtual bool Decrypt(StringPiece nonce,
+                       StringPiece associated_data,
+                       StringPiece ciphertext,
+                       unsigned char* output,
+                       size_t* output_length) OVERRIDE {
+    CHECK(false) << "Not implemented";
+    return false;
+  }
+  virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+                                  StringPiece associated_data,
+                                  StringPiece ciphertext) OVERRIDE {
+    sequence_number_ = sequence_number;
+    associated_data_ = associated_data.as_string();
+    ciphertext_ = ciphertext.as_string();
+    return new QuicData(ciphertext.data(), ciphertext.length());
+  }
+  virtual StringPiece GetKey() const OVERRIDE {
+    return StringPiece();
+  }
+  virtual StringPiece GetNoncePrefix() const OVERRIDE {
+    return StringPiece();
+  }
+  QuicPacketSequenceNumber sequence_number_;
+  string associated_data_;
+  string ciphertext_;
+};
+
+class TestQuicVisitor : public ::net::QuicFramerVisitorInterface {
+ public:
+  TestQuicVisitor()
+      : error_count_(0),
+        version_mismatch_(0),
+        packet_count_(0),
+        frame_count_(0),
+        fec_count_(0),
+        complete_packets_(0),
+        revived_packets_(0),
+        accept_packet_(true),
+        accept_public_header_(true) {
+  }
+
+  virtual ~TestQuicVisitor() {
+    STLDeleteElements(&stream_frames_);
+    STLDeleteElements(&ack_frames_);
+    STLDeleteElements(&congestion_feedback_frames_);
+    STLDeleteElements(&stop_waiting_frames_);
+    STLDeleteElements(&ping_frames_);
+    STLDeleteElements(&fec_data_);
+  }
+
+  virtual void OnError(QuicFramer* f) OVERRIDE {
+    DVLOG(1) << "QuicFramer Error: " << QuicUtils::ErrorToString(f->error())
+             << " (" << f->error() << ")";
+    ++error_count_;
+  }
+
+  virtual void OnPacket() OVERRIDE {}
+
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) OVERRIDE {
+    public_reset_packet_.reset(new QuicPublicResetPacket(packet));
+  }
+
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) OVERRIDE {
+    version_negotiation_packet_.reset(new QuicVersionNegotiationPacket(packet));
+  }
+
+  virtual void OnRevivedPacket() OVERRIDE {
+    ++revived_packets_;
+  }
+
+  virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+    DVLOG(1) << "QuicFramer Version Mismatch, version: " << version;
+    ++version_mismatch_;
+    return true;
+  }
+
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) OVERRIDE {
+    public_header_.reset(new QuicPacketPublicHeader(header));
+    return accept_public_header_;
+  }
+
+  virtual bool OnUnauthenticatedHeader(
+      const QuicPacketHeader& header) OVERRIDE {
+    return true;
+  }
+
+  virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE {}
+
+  virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE {
+    ++packet_count_;
+    header_.reset(new QuicPacketHeader(header));
+    return accept_packet_;
+  }
+
+  virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+    ++frame_count_;
+    stream_frames_.push_back(new QuicStreamFrame(frame));
+    return true;
+  }
+
+  virtual void OnFecProtectedPayload(StringPiece payload) OVERRIDE {
+    fec_protected_payload_ = payload.as_string();
+  }
+
+  virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE {
+    ++frame_count_;
+    ack_frames_.push_back(new QuicAckFrame(frame));
+    return true;
+  }
+
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) OVERRIDE {
+    ++frame_count_;
+    congestion_feedback_frames_.push_back(
+        new QuicCongestionFeedbackFrame(frame));
+    return true;
+  }
+
+  virtual bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) OVERRIDE {
+    ++frame_count_;
+    stop_waiting_frames_.push_back(new QuicStopWaitingFrame(frame));
+    return true;
+  }
+
+  virtual bool OnPingFrame(const QuicPingFrame& frame) OVERRIDE {
+    ++frame_count_;
+    ping_frames_.push_back(new QuicPingFrame(frame));
+    return true;
+  }
+
+  virtual void OnFecData(const QuicFecData& fec) OVERRIDE {
+    ++fec_count_;
+    fec_data_.push_back(new QuicFecData(fec));
+  }
+
+  virtual void OnPacketComplete() OVERRIDE {
+    ++complete_packets_;
+  }
+
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE {
+    rst_stream_frame_ = frame;
+    return true;
+  }
+
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) OVERRIDE {
+    connection_close_frame_ = frame;
+    return true;
+  }
+
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE {
+    goaway_frame_ = frame;
+    return true;
+  }
+
+  virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame)
+      OVERRIDE {
+    window_update_frame_ = frame;
+    return true;
+  }
+
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE {
+    blocked_frame_ = frame;
+    return true;
+  }
+
+  // Counters from the visitor_ callbacks.
+  int error_count_;
+  int version_mismatch_;
+  int packet_count_;
+  int frame_count_;
+  int fec_count_;
+  int complete_packets_;
+  int revived_packets_;
+  bool accept_packet_;
+  bool accept_public_header_;
+
+  scoped_ptr<QuicPacketHeader> header_;
+  scoped_ptr<QuicPacketPublicHeader> public_header_;
+  scoped_ptr<QuicPublicResetPacket> public_reset_packet_;
+  scoped_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+  vector<QuicStreamFrame*> stream_frames_;
+  vector<QuicAckFrame*> ack_frames_;
+  vector<QuicCongestionFeedbackFrame*> congestion_feedback_frames_;
+  vector<QuicStopWaitingFrame*> stop_waiting_frames_;
+  vector<QuicPingFrame*> ping_frames_;
+  vector<QuicFecData*> fec_data_;
+  string fec_protected_payload_;
+  QuicRstStreamFrame rst_stream_frame_;
+  QuicConnectionCloseFrame connection_close_frame_;
+  QuicGoAwayFrame goaway_frame_;
+  QuicWindowUpdateFrame window_update_frame_;
+  QuicBlockedFrame blocked_frame_;
+};
+
+class QuicFramerTest : public ::testing::TestWithParam<QuicVersion> {
+ public:
+  QuicFramerTest()
+      : encrypter_(new test::TestEncrypter()),
+        decrypter_(new test::TestDecrypter()),
+        start_(QuicTime::Zero().Add(QuicTime::Delta::FromMicroseconds(0x10))),
+        framer_(QuicSupportedVersions(), start_, true) {
+    version_ = GetParam();
+    framer_.set_version(version_);
+    framer_.SetDecrypter(decrypter_, ENCRYPTION_NONE);
+    framer_.SetEncrypter(ENCRYPTION_NONE, encrypter_);
+    framer_.set_visitor(&visitor_);
+    framer_.set_received_entropy_calculator(&entropy_calculator_);
+  }
+
+  // Helper function to get unsigned char representation of digit in the
+  // units place of the current QUIC version number.
+  unsigned char GetQuicVersionDigitOnes() {
+    return static_cast<unsigned char> ('0' + version_%10);
+  }
+
+  // Helper function to get unsigned char representation of digit in the
+  // tens place of the current QUIC version number.
+  unsigned char GetQuicVersionDigitTens() {
+    return static_cast<unsigned char> ('0' + (version_/10)%10);
+  }
+
+  bool CheckEncryption(QuicPacketSequenceNumber sequence_number,
+                       QuicPacket* packet) {
+    if (sequence_number != encrypter_->sequence_number_) {
+      LOG(ERROR) << "Encrypted incorrect packet sequence number.  expected "
+                 << sequence_number << " actual: "
+                 << encrypter_->sequence_number_;
+      return false;
+    }
+    if (packet->AssociatedData() != encrypter_->associated_data_) {
+      LOG(ERROR) << "Encrypted incorrect associated data.  expected "
+                 << packet->AssociatedData() << " actual: "
+                 << encrypter_->associated_data_;
+      return false;
+    }
+    if (packet->Plaintext() != encrypter_->plaintext_) {
+      LOG(ERROR) << "Encrypted incorrect plaintext data.  expected "
+                 << packet->Plaintext() << " actual: "
+                 << encrypter_->plaintext_;
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckDecryption(const QuicEncryptedPacket& encrypted,
+                       bool includes_version) {
+    if (visitor_.header_->packet_sequence_number !=
+        decrypter_->sequence_number_) {
+      LOG(ERROR) << "Decrypted incorrect packet sequence number.  expected "
+                 << visitor_.header_->packet_sequence_number << " actual: "
+                 << decrypter_->sequence_number_;
+      return false;
+    }
+    if (QuicFramer::GetAssociatedDataFromEncryptedPacket(
+            encrypted, PACKET_8BYTE_CONNECTION_ID,
+            includes_version, PACKET_6BYTE_SEQUENCE_NUMBER) !=
+        decrypter_->associated_data_) {
+      LOG(ERROR) << "Decrypted incorrect associated data.  expected "
+                 << QuicFramer::GetAssociatedDataFromEncryptedPacket(
+                     encrypted, PACKET_8BYTE_CONNECTION_ID,
+                     includes_version, PACKET_6BYTE_SEQUENCE_NUMBER)
+                 << " actual: " << decrypter_->associated_data_;
+      return false;
+    }
+    StringPiece ciphertext(encrypted.AsStringPiece().substr(
+        GetStartOfEncryptedData(PACKET_8BYTE_CONNECTION_ID, includes_version,
+                                PACKET_6BYTE_SEQUENCE_NUMBER)));
+    if (ciphertext != decrypter_->ciphertext_) {
+      LOG(ERROR) << "Decrypted incorrect ciphertext data.  expected "
+                 << ciphertext << " actual: "
+                 << decrypter_->ciphertext_;
+      return false;
+    }
+    return true;
+  }
+
+  char* AsChars(unsigned char* data) {
+    return reinterpret_cast<char*>(data);
+  }
+
+  void CheckProcessingFails(unsigned char* packet,
+                            size_t len,
+                            string expected_error,
+                            QuicErrorCode error_code) {
+    QuicEncryptedPacket encrypted(AsChars(packet), len, false);
+    EXPECT_FALSE(framer_.ProcessPacket(encrypted)) << "len: " << len;
+    EXPECT_EQ(expected_error, framer_.detailed_error()) << "len: " << len;
+    EXPECT_EQ(error_code, framer_.error()) << "len: " << len;
+  }
+
+  // Checks if the supplied string matches data in the supplied StreamFrame.
+  void CheckStreamFrameData(string str, QuicStreamFrame* frame) {
+    scoped_ptr<string> frame_data(frame->GetDataAsString());
+    EXPECT_EQ(str, *frame_data);
+  }
+
+  void CheckStreamFrameBoundaries(unsigned char* packet,
+                                  size_t stream_id_size,
+                                  bool include_version) {
+    // Now test framing boundaries.
+    for (size_t i = kQuicFrameTypeSize; i < GetMinStreamFrameSize(); ++i) {
+      string expected_error;
+      if (i < kQuicFrameTypeSize + stream_id_size) {
+        expected_error = "Unable to read stream_id.";
+      } else if (i < kQuicFrameTypeSize + stream_id_size +
+                 kQuicMaxStreamOffsetSize) {
+        expected_error = "Unable to read offset.";
+      } else {
+        expected_error = "Unable to read frame data.";
+      }
+      CheckProcessingFails(
+          packet,
+          i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, include_version,
+                                  PACKET_6BYTE_SEQUENCE_NUMBER,
+                                  NOT_IN_FEC_GROUP),
+          expected_error, QUIC_INVALID_STREAM_DATA);
+    }
+  }
+
+  void CheckCalculatePacketSequenceNumber(
+      QuicPacketSequenceNumber expected_sequence_number,
+      QuicPacketSequenceNumber last_sequence_number) {
+    QuicPacketSequenceNumber wire_sequence_number =
+        expected_sequence_number & kMask;
+    QuicFramerPeer::SetLastSequenceNumber(&framer_, last_sequence_number);
+    EXPECT_EQ(expected_sequence_number,
+              QuicFramerPeer::CalculatePacketSequenceNumberFromWire(
+                  &framer_, PACKET_6BYTE_SEQUENCE_NUMBER, wire_sequence_number))
+        << "last_sequence_number: " << last_sequence_number
+        << " wire_sequence_number: " << wire_sequence_number;
+  }
+
+  QuicPacket* BuildDataPacket(const QuicPacketHeader& header,
+                              const QuicFrames& frames) {
+    return BuildUnsizedDataPacket(&framer_, header, frames).packet;
+  }
+
+  test::TestEncrypter* encrypter_;
+  test::TestDecrypter* decrypter_;
+  QuicVersion version_;
+  QuicTime start_;
+  QuicFramer framer_;
+  test::TestQuicVisitor visitor_;
+  test::TestEntropyCalculator entropy_calculator_;
+};
+
+// Run all framer tests with all supported versions of QUIC.
+INSTANTIATE_TEST_CASE_P(QuicFramerTests,
+                        QuicFramerTest,
+                        ::testing::ValuesIn(kSupportedQuicVersions));
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearEpochStart) {
+  // A few quick manual sanity checks
+  CheckCalculatePacketSequenceNumber(GG_UINT64_C(1), GG_UINT64_C(0));
+  CheckCalculatePacketSequenceNumber(kEpoch + 1, kMask);
+  CheckCalculatePacketSequenceNumber(kEpoch, kMask);
+
+  // Cases where the last number was close to the start of the range
+  for (uint64 last = 0; last < 10; last++) {
+    // Small numbers should not wrap (even if they're out of order).
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(j, last);
+    }
+
+    // Large numbers should not wrap either (because we're near 0 already).
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(kEpoch - 1 - j, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearEpochEnd) {
+  // Cases where the last number was close to the end of the range
+  for (uint64 i = 0; i < 10; i++) {
+    QuicPacketSequenceNumber last = kEpoch - i;
+
+    // Small numbers should wrap.
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(kEpoch + j, last);
+    }
+
+    // Large numbers should not (even if they're out of order).
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(kEpoch - 1 - j, last);
+    }
+  }
+}
+
+// Next check where we're in a non-zero epoch to verify we handle
+// reverse wrapping, too.
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearPrevEpoch) {
+  const uint64 prev_epoch = 1 * kEpoch;
+  const uint64 cur_epoch = 2 * kEpoch;
+  // Cases where the last number was close to the start of the range
+  for (uint64 i = 0; i < 10; i++) {
+    uint64 last = cur_epoch + i;
+    // Small number should not wrap (even if they're out of order).
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(cur_epoch + j, last);
+    }
+
+    // But large numbers should reverse wrap.
+    for (uint64 j = 0; j < 10; j++) {
+      uint64 num = kEpoch - 1 - j;
+      CheckCalculatePacketSequenceNumber(prev_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearNextEpoch) {
+  const uint64 cur_epoch = 2 * kEpoch;
+  const uint64 next_epoch = 3 * kEpoch;
+  // Cases where the last number was close to the end of the range
+  for (uint64 i = 0; i < 10; i++) {
+    QuicPacketSequenceNumber last = next_epoch - 1 - i;
+
+    // Small numbers should wrap.
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(next_epoch + j, last);
+    }
+
+    // but large numbers should not (even if they're out of order).
+    for (uint64 j = 0; j < 10; j++) {
+      uint64 num = kEpoch - 1 - j;
+      CheckCalculatePacketSequenceNumber(cur_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearNextMax) {
+  const uint64 max_number = numeric_limits<uint64>::max();
+  const uint64 max_epoch = max_number & ~kMask;
+
+  // Cases where the last number was close to the end of the range
+  for (uint64 i = 0; i < 10; i++) {
+    // Subtract 1, because the expected next sequence number is 1 more than the
+    // last sequence number.
+    QuicPacketSequenceNumber last = max_number - i - 1;
+
+    // Small numbers should not wrap, because they have nowhere to go.
+    for (uint64 j = 0; j < 10; j++) {
+      CheckCalculatePacketSequenceNumber(max_epoch + j, last);
+    }
+
+    // Large numbers should not wrap either.
+    for (uint64 j = 0; j < 10; j++) {
+      uint64 num = kEpoch - 1 - j;
+      CheckCalculatePacketSequenceNumber(max_epoch + num, last);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, EmptyPacket) {
+  char packet[] = { 0x00 };
+  QuicEncryptedPacket encrypted(packet, 0, false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
+}
+
+TEST_P(QuicFramerTest, LargePacket) {
+  unsigned char packet[kMaxPacketSize + 1] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  memset(packet + GetPacketHeaderSize(
+             PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+             PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP), 0,
+         kMaxPacketSize - GetPacketHeaderSize(
+             PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+             PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) + 1);
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+
+  ASSERT_TRUE(visitor_.header_.get());
+  // Make sure we've parsed the packet header, so we can send an error.
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  // Make sure the correct error is propagated.
+  EXPECT_EQ(QUIC_PACKET_TOO_LARGE, framer_.error());
+}
+
+TEST_P(QuicFramerTest, PacketHeader) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith4ByteConnectionId) {
+  QuicFramerPeer::SetLastSerializedConnectionId(
+      &framer_, GG_UINT64_C(0xFEDCBA9876543210));
+
+  unsigned char packet[] = {
+    // public flags (4 byte connection_id)
+    0x38,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_4BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(PACKET_4BYTE_CONNECTION_ID,
+                                           !kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(PACKET_4BYTE_CONNECTION_ID,
+                                         !kIncludeVersion)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(PACKET_4BYTE_CONNECTION_ID,
+                                     !kIncludeVersion)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeader1ByteConnectionId) {
+  QuicFramerPeer::SetLastSerializedConnectionId(
+      &framer_, GG_UINT64_C(0xFEDCBA9876543210));
+
+  unsigned char packet[] = {
+    // public flags (1 byte connection_id)
+    0x34,
+    // connection_id
+    0x10,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_1BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(PACKET_1BYTE_CONNECTION_ID,
+                                           !kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(PACKET_1BYTE_CONNECTION_ID,
+                                         !kIncludeVersion)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(PACKET_1BYTE_CONNECTION_ID,
+                                     !kIncludeVersion)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith0ByteConnectionId) {
+  QuicFramerPeer::SetLastSerializedConnectionId(
+      &framer_, GG_UINT64_C(0xFEDCBA9876543210));
+
+  unsigned char packet[] = {
+    // public flags (0 byte connection_id)
+    0x30,
+    // connection_id
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_0BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(PACKET_0BYTE_CONNECTION_ID,
+                                           !kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(PACKET_0BYTE_CONNECTION_ID,
+                                         !kIncludeVersion)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(PACKET_0BYTE_CONNECTION_ID,
+                                     !kIncludeVersion)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWithVersionFlag) {
+  unsigned char packet[] = {
+    // public flags (version)
+    0x3D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_TRUE(visitor_.header_->public_header.version_flag);
+  EXPECT_EQ(GetParam(), visitor_.header_->public_header.versions[0]);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, kIncludeVersion,
+                               PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < kVersionOffset) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i <  GetSequenceNumberOffset(kIncludeVersion)) {
+      expected_error = "Unable to read protocol version.";
+    } else if (i < GetPrivateFlagsOffset(kIncludeVersion)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(kIncludeVersion)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith4ByteSequenceNumber) {
+  QuicFramerPeer::SetLastSequenceNumber(&framer_,
+                                        GG_UINT64_C(0x123456789ABA));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id and 4 byte sequence number)
+    0x2C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_4BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+                                         PACKET_4BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(!kIncludeVersion,
+                                     PACKET_4BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith2ByteSequenceNumber) {
+  QuicFramerPeer::SetLastSequenceNumber(&framer_,
+                                        GG_UINT64_C(0x123456789ABA));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id and 2 byte sequence number)
+    0x1C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_2BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+                                         PACKET_2BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(!kIncludeVersion,
+                                     PACKET_2BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith1ByteSequenceNumber) {
+  QuicFramerPeer::SetLastSequenceNumber(&framer_,
+                                        GG_UINT64_C(0x123456789ABA));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id and 1 byte sequence number)
+    0x0C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC,
+    // private flags
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_MISSING_PAYLOAD, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+  EXPECT_FALSE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(0, visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  // Now test framing boundaries.
+  for (size_t i = 0;
+       i < GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                               PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+       ++i) {
+    string expected_error;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+      expected_error = "Unable to read ConnectionId.";
+    } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+                                         PACKET_1BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read sequence number.";
+    } else if (i < GetFecGroupOffset(!kIncludeVersion,
+                                     PACKET_1BYTE_SEQUENCE_NUMBER)) {
+      expected_error = "Unable to read private flags.";
+    } else {
+      expected_error = "Unable to read first fec protected packet offset.";
+    }
+    CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+  }
+}
+
+TEST_P(QuicFramerTest, InvalidPublicFlag) {
+  unsigned char packet[] = {
+    // public flags: all flags set but the public reset flag and version flag.
+    0xFC,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  CheckProcessingFails(packet,
+                       arraysize(packet),
+                       "Illegal public flags value.",
+                       QUIC_INVALID_PACKET_HEADER);
+
+  // Now turn off validation.
+  framer_.set_validate_flags(false);
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+};
+
+TEST_P(QuicFramerTest, InvalidPublicFlagWithMatchingVersions) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id and version flag and an unknown flag)
+    0x4D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  CheckProcessingFails(packet,
+                       arraysize(packet),
+                       "Illegal public flags value.",
+                       QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, LargePublicFlagWithMismatchedVersions) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id, version flag and an unknown flag)
+    0x7D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', '0', '0',
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(0, visitor_.frame_count_);
+  EXPECT_EQ(1, visitor_.version_mismatch_);
+};
+
+TEST_P(QuicFramerTest, InvalidPrivateFlag) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x10,
+
+    // frame type (padding)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+  CheckProcessingFails(packet,
+                       arraysize(packet),
+                       "Illegal private flags value.",
+                       QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, InvalidFECGroupOffset) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00,
+    // private flags (fec group)
+    0x02,
+    // first fec protected packet offset
+    0x10
+  };
+  CheckProcessingFails(packet,
+                       arraysize(packet),
+                       "First fec protected packet offset must be less "
+                       "than the sequence number.",
+                       QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, PaddingFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    // Ignored data (which in this case is a stream frame)
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(0u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  // A packet with no frames is not acceptable.
+  CheckProcessingFails(
+      packet,
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                          PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+      "Packet has no frames.", QUIC_MISSING_PAYLOAD);
+}
+
+TEST_P(QuicFramerTest, StreamFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(static_cast<uint64>(0x01020304),
+            visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+
+  // Now test framing boundaries.
+  CheckStreamFrameBoundaries(packet, kQuicMaxStreamIdSize, !kIncludeVersion);
+}
+
+TEST_P(QuicFramerTest, StreamFrame3ByteStreamId) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFE,
+    // stream id
+    0x04, 0x03, 0x02,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(GG_UINT64_C(0x00020304),
+            visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+
+  // Now test framing boundaries.
+  const size_t stream_id_size = 3;
+  CheckStreamFrameBoundaries(packet, stream_id_size, !kIncludeVersion);
+}
+
+TEST_P(QuicFramerTest, StreamFrame2ByteStreamId) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFD,
+    // stream id
+    0x04, 0x03,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(static_cast<uint64>(0x00000304),
+            visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+
+  // Now test framing boundaries.
+  const size_t stream_id_size = 2;
+  CheckStreamFrameBoundaries(packet, stream_id_size, !kIncludeVersion);
+}
+
+TEST_P(QuicFramerTest, StreamFrame1ByteStreamId) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFC,
+    // stream id
+    0x04,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(static_cast<uint64>(0x00000004),
+            visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+
+  // Now test framing boundaries.
+  const size_t stream_id_size = 1;
+  CheckStreamFrameBoundaries(packet, stream_id_size, !kIncludeVersion);
+}
+
+TEST_P(QuicFramerTest, StreamFrameWithVersion) {
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x3D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(visitor_.header_->public_header.version_flag);
+  EXPECT_EQ(GetParam(), visitor_.header_->public_header.versions[0]);
+  EXPECT_TRUE(CheckDecryption(encrypted, kIncludeVersion));
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(static_cast<uint64>(0x01020304),
+            visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+
+  // Now test framing boundaries.
+  CheckStreamFrameBoundaries(packet, kQuicMaxStreamIdSize, kIncludeVersion);
+}
+
+TEST_P(QuicFramerTest, RejectPacket) {
+  visitor_.accept_packet_ = false;
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  ASSERT_EQ(0u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+}
+
+TEST_P(QuicFramerTest, RejectPublicHeader) {
+  visitor_.accept_public_header_ = false;
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_header_.get());
+  ASSERT_FALSE(visitor_.header_.get());
+}
+
+TEST_P(QuicFramerTest, RevivedStreamFrame) {
+  unsigned char payload[] = {
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = true;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  // Do not encrypt the payload because the revived payload is post-encryption.
+  EXPECT_TRUE(framer_.ProcessRevivedPacket(&header,
+                                           StringPiece(AsChars(payload),
+                                                       arraysize(payload))));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_EQ(1, visitor_.revived_packets_);
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.header_->public_header.connection_id);
+  EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+  EXPECT_TRUE(visitor_.header_->fec_flag);
+  EXPECT_TRUE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(1 << (header.packet_sequence_number % 8),
+            visitor_.header_->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.header_->packet_sequence_number);
+  EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+}
+
+TEST_P(QuicFramerTest, StreamFrameInFecGroup) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x12, 0x34,
+    // private flags (fec group)
+    0x02,
+    // first fec protected packet offset
+    0x02,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+  EXPECT_EQ(IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+  EXPECT_EQ(GG_UINT64_C(0x341256789ABA),
+            visitor_.header_->fec_group);
+  const size_t fec_offset =
+      GetStartOfFecProtectedData(PACKET_8BYTE_CONNECTION_ID,
+                                 !kIncludeVersion,
+                                 PACKET_6BYTE_SEQUENCE_NUMBER);
+  EXPECT_EQ(
+      string(AsChars(packet) + fec_offset, arraysize(packet) - fec_offset),
+      visitor_.fec_protected_payload_);
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+            visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("hello world!", visitor_.stream_frames_[0]);
+}
+
+TEST_P(QuicFramerTest, AckFramev22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumMissingPacketOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else {
+      expected_error = "Unable to read num revived packets.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+
+TEST_P(QuicFramerTest, AckFrameTwoTimestamp) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // Number of timestamps.
+    0x02,
+    // Delta from largest observed.
+    0x01,
+    // Delta time.
+    0x10, 0x32, 0x54, 0x76,
+    // Delta from largest observed.
+    0x02,
+    // Delta time.
+    0x10, 0x32,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  ASSERT_EQ(2u, frame.received_packet_times.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumTimestampsOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kTimestampDeltaLargestObserved1 = kNumTimestampsOffset +
+      kQuicNumTimestampsSize;
+  const size_t kTimestampTimeDeltaLargestObserved1 =
+      kTimestampDeltaLargestObserved1 + 1;
+  const size_t kTimestampDeltaLargestObserved2 =
+      kTimestampTimeDeltaLargestObserved1 + 4;
+  const size_t kTimestampTimeDeltaLargestObserved2 =
+      kTimestampDeltaLargestObserved2 + 1;
+  const size_t kNumMissingPacketOffset =
+      kTimestampTimeDeltaLargestObserved2 + 2;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumTimestampsOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kTimestampDeltaLargestObserved1) {
+      expected_error = "Unable to read num received packets.";
+    } else if (i < kTimestampTimeDeltaLargestObserved1) {
+      expected_error = "Unable to read sequence delta in received packets.";
+    } else if (i < kTimestampDeltaLargestObserved2) {
+      expected_error = "Unable to read time delta in received packets.";
+    } else if (i < kTimestampTimeDeltaLargestObserved2) {
+      expected_error = "Unable to read sequence delta in received packets.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error =
+          "Unable to read incremental time delta in received packets.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else {
+      expected_error = "Unable to read num revived packets.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+
+TEST_P(QuicFramerTest, AckFrameOneTimestamp) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // Number of timestamps.
+    0x01,
+    // Delta from largest observed.
+    0x01,
+    // Delta time.
+    0x10, 0x32, 0x54, 0x76,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  ASSERT_EQ(1u, frame.received_packet_times.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumTimestampsOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kTimestampDeltaLargestObserved = kNumTimestampsOffset +
+      kQuicNumTimestampsSize;
+  const size_t kTimestampTimeDeltaLargestObserved =
+    kTimestampDeltaLargestObserved + 1;
+  const size_t kNumMissingPacketOffset = kTimestampTimeDeltaLargestObserved + 4;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumTimestampsOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kTimestampDeltaLargestObserved) {
+      expected_error = "Unable to read num received packets.";
+    } else if (i < kTimestampTimeDeltaLargestObserved) {
+      expected_error = "Unable to read sequence delta in received packets.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error = "Unable to read time delta in received packets.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else {
+      expected_error = "Unable to read num revived packets.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+
+TEST_P(QuicFramerTest, AckFrame) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // Number of timestamps.
+    0x00,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumTimestampsOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kNumMissingPacketOffset = kNumTimestampsOffset +
+      kQuicNumTimestampsSize;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumTimestampsOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error = "Unable to read num received packets.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else {
+      expected_error = "Unable to read num revived packets.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, AckFrameRevivedPackets) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num received packets.
+    0x00,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x01,
+    // Revived packet sequence number.
+    0xBE, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumTimestampsOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kNumMissingPacketOffset = kNumTimestampsOffset +
+      kQuicNumTimestampsSize;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketSequenceNumberLength = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketSequenceNumberLength +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kReceivedEntropyOffset) {
+      expected_error = "Unable to read least unacked delta.";
+    } else if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumTimestampsOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error = "Unable to read num received packets.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else if (i < kRevivedPacketSequenceNumberLength) {
+      expected_error = "Unable to read num revived packets.";
+    } else {
+      expected_error = "Unable to read revived packet.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, AckFrameRevivedPacketsv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packets
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // Number of revived packets.
+    0x01,
+    // Revived packet sequence number.
+    0xBE, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Number of revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.largest_observed);
+  ASSERT_EQ(1u, frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      frame.missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+  const size_t kReceivedEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+      kQuicEntropyHashSize;
+  const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  const size_t kNumMissingPacketOffset = kMissingDeltaTimeOffset +
+      kQuicDeltaTimeLargestObservedSize;
+  const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+      kNumberOfNackRangesSize;
+  const size_t kMissingPacketsRange = kMissingPacketsOffset +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketsLength = kMissingPacketsRange +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  const size_t kRevivedPacketSequenceNumberLength = kRevivedPacketsLength +
+      PACKET_1BYTE_SEQUENCE_NUMBER;
+  // Now test framing boundaries.
+  const size_t ack_frame_size = kRevivedPacketSequenceNumberLength +
+      PACKET_6BYTE_SEQUENCE_NUMBER;
+  for (size_t i = kQuicFrameTypeSize; i < ack_frame_size; ++i) {
+    string expected_error;
+    if (i < kReceivedEntropyOffset) {
+      expected_error = "Unable to read least unacked delta.";
+    } else if (i < kLargestObservedOffset) {
+      expected_error = "Unable to read entropy hash for received packets.";
+    } else if (i < kMissingDeltaTimeOffset) {
+      expected_error = "Unable to read largest observed.";
+    } else if (i < kNumMissingPacketOffset) {
+      expected_error = "Unable to read delta time largest observed.";
+    } else if (i < kMissingPacketsOffset) {
+      expected_error = "Unable to read num missing packet ranges.";
+    } else if (i < kMissingPacketsRange) {
+      expected_error = "Unable to read missing sequence number delta.";
+    } else if (i < kRevivedPacketsLength) {
+      expected_error = "Unable to read missing sequence number range.";
+    } else if (i < kRevivedPacketSequenceNumberLength) {
+      expected_error = "Unable to read num revived packets.";
+    } else {
+      expected_error = "Unable to read revived packet.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_ACK_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, AckFrameNoNacksv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (no nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x4C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame* frame = visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame->largest_observed);
+  ASSERT_EQ(0u, frame->missing_packets.size());
+
+  // Verify that the packet re-serializes identically.
+  QuicFrames frames;
+  frames.push_back(QuicFrame(frame));
+  scoped_ptr<QuicPacket> data(BuildDataPacket(*visitor_.header_, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, AckFrameNoNacks) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (no nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x4C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // Number of received packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame* frame = visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame->largest_observed);
+  ASSERT_EQ(0u, frame->missing_packets.size());
+
+  // Verify that the packet re-serializes identically.
+  QuicFrames frames;
+  frames.push_back(QuicFrame(frame));
+  scoped_ptr<QuicPacket> data(BuildDataPacket(*visitor_.header_, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, AckFrame500Nacksv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges
+    0x02,
+    // missing packet delta
+    0x01,
+    // 243 more missing packets in range.
+    // The ranges are listed in this order so the re-constructed packet matches.
+    0xF3,
+    // No gap between ranges
+    0x00,
+    // 255 more missing packets in range.
+    0xFF,
+    // No revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame* frame = visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame->largest_observed);
+  EXPECT_EQ(0u, frame->revived_packets.size());
+  ASSERT_EQ(500u, frame->missing_packets.size());
+  SequenceNumberSet::const_iterator first_missing_iter =
+      frame->missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE) - 499, *first_missing_iter);
+  SequenceNumberSet::const_reverse_iterator last_missing_iter =
+      frame->missing_packets.rbegin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *last_missing_iter);
+
+  // Verify that the packet re-serializes identically.
+  QuicFrames frames;
+  frames.push_back(QuicFrame(frame));
+  scoped_ptr<QuicPacket> data(BuildDataPacket(*visitor_.header_, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, AckFrame500Nacks) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0xBA,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // No received packets.
+    0x00,
+    // num missing packet ranges
+    0x02,
+    // missing packet delta
+    0x01,
+    // 243 more missing packets in range.
+    // The ranges are listed in this order so the re-constructed packet matches.
+    0xF3,
+    // No gap between ranges
+    0x00,
+    // 255 more missing packets in range.
+    0xFF,
+    // No revived packets.
+    0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame* frame = visitor_.ack_frames_[0];
+  EXPECT_EQ(0xBA, frame->entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame->largest_observed);
+  EXPECT_EQ(0u, frame->revived_packets.size());
+  ASSERT_EQ(500u, frame->missing_packets.size());
+  SequenceNumberSet::const_iterator first_missing_iter =
+      frame->missing_packets.begin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE) - 499, *first_missing_iter);
+  SequenceNumberSet::const_reverse_iterator last_missing_iter =
+      frame->missing_packets.rbegin();
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *last_missing_iter);
+
+  // Verify that the packet re-serializes identically.
+  QuicFrames frames;
+  frames.push_back(QuicFrame(frame));
+  scoped_ptr<QuicPacket> data(BuildDataPacket(*visitor_.header_, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameTCP) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (congestion feedback frame)
+    0x20,
+    // congestion feedback type (tcp)
+    0x00,
+    // ack_frame.feedback.tcp.receive_window
+    0x03, 0x04,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.congestion_feedback_frames_.size());
+  const QuicCongestionFeedbackFrame& frame =
+      *visitor_.congestion_feedback_frames_[0];
+  ASSERT_EQ(kTCP, frame.type);
+  EXPECT_EQ(0x4030u, frame.tcp.receive_window);
+
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize; i < 4; ++i) {
+    string expected_error;
+    if (i < 2) {
+      expected_error = "Unable to read congestion feedback type.";
+    } else if (i < 4) {
+      expected_error = "Unable to read receive window.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_CONGESTION_FEEDBACK_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameInvalidFeedback) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (congestion feedback frame)
+    0x20,
+    // congestion feedback type (invalid)
+    0x03,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+  EXPECT_EQ(QUIC_INVALID_CONGESTION_FEEDBACK_DATA, framer_.error());
+}
+
+TEST_P(QuicFramerTest, StopWaitingFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x06,
+    // entropy hash of sent packets till least awaiting - 1.
+    0xAB,
+    // least packet sequence number awaiting an ack, delta from sequence number.
+    0x08, 0x00, 0x00, 0x00,
+    0x00, 0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  ASSERT_EQ(1u, visitor_.stop_waiting_frames_.size());
+  const QuicStopWaitingFrame& frame = *visitor_.stop_waiting_frames_[0];
+  EXPECT_EQ(0xAB, frame.entropy_hash);
+  EXPECT_EQ(GG_UINT64_C(0x0123456789AA0), frame.least_unacked);
+
+  const size_t kSentEntropyOffset = kQuicFrameTypeSize;
+  const size_t kLeastUnackedOffset = kSentEntropyOffset + kQuicEntropyHashSize;
+  const size_t frame_size = 7;
+  for (size_t i = kQuicFrameTypeSize; i < frame_size; ++i) {
+    string expected_error;
+    if (i < kLeastUnackedOffset) {
+      expected_error = "Unable to read entropy hash for sent packets.";
+    } else {
+      expected_error = "Unable to read least unacked delta.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_STOP_WAITING_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, RstStreamFrameQuic) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (rst stream frame)
+    0x01,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+
+    // sent byte offset
+    0x01, 0x02, 0x03, 0x04,
+    0x05, 0x06, 0x07, 0x08,
+
+    // error code
+    0x01, 0x00, 0x00, 0x00,
+
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.rst_stream_frame_.stream_id);
+  EXPECT_EQ(0x01, visitor_.rst_stream_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.rst_stream_frame_.error_details);
+  EXPECT_EQ(GG_UINT64_C(0x0807060504030201),
+            visitor_.rst_stream_frame_.byte_offset);
+
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize;
+       i < QuicFramer::GetMinRstStreamFrameSize(); ++i) {
+    string expected_error;
+    if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+      expected_error = "Unable to read stream_id.";
+    } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+                       + kQuicMaxStreamOffsetSize) {
+      expected_error = "Unable to read rst stream sent byte offset.";
+    } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+                       + kQuicMaxStreamOffsetSize + kQuicErrorCodeSize) {
+      expected_error = "Unable to read rst stream error code.";
+    } else {
+      expected_error = "Unable to read rst stream error details.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_RST_STREAM_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, ConnectionCloseFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x11, 0x00, 0x00, 0x00,
+
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+  EXPECT_EQ(0x11, visitor_.connection_close_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.connection_close_frame_.error_details);
+
+  ASSERT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize;
+       i < QuicFramer::GetMinConnectionCloseFrameSize(); ++i) {
+    string expected_error;
+    if (i < kQuicFrameTypeSize + kQuicErrorCodeSize) {
+      expected_error = "Unable to read connection close error code.";
+    } else {
+      expected_error = "Unable to read connection close error details.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_CONNECTION_CLOSE_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, GoAwayFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x09, 0x00, 0x00, 0x00,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(GG_UINT64_C(0x01020304),
+            visitor_.goaway_frame_.last_good_stream_id);
+  EXPECT_EQ(0x9, visitor_.goaway_frame_.error_code);
+  EXPECT_EQ("because I can", visitor_.goaway_frame_.reason_phrase);
+
+  const size_t reason_size = arraysize("because I can") - 1;
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize;
+       i < QuicFramer::GetMinGoAwayFrameSize() + reason_size; ++i) {
+    string expected_error;
+    if (i < kQuicFrameTypeSize + kQuicErrorCodeSize) {
+      expected_error = "Unable to read go away error code.";
+    } else if (i < kQuicFrameTypeSize + kQuicErrorCodeSize +
+               kQuicMaxStreamIdSize) {
+      expected_error = "Unable to read last good stream id.";
+    } else {
+      expected_error = "Unable to read goaway reason.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_GOAWAY_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, WindowUpdateFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (window update frame)
+    0x04,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // byte offset
+    0x05, 0x06, 0x07, 0x08,
+    0x09, 0x0a, 0x0b, 0x0c,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(GG_UINT64_C(0x01020304),
+            visitor_.window_update_frame_.stream_id);
+  EXPECT_EQ(GG_UINT64_C(0x0c0b0a0908070605),
+            visitor_.window_update_frame_.byte_offset);
+
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize;
+       i < QuicFramer::GetWindowUpdateFrameSize(); ++i) {
+    string expected_error;
+    if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+      expected_error = "Unable to read stream_id.";
+    } else {
+      expected_error = "Unable to read window byte_offset.";
+    }
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_WINDOW_UPDATE_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, BlockedFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (blocked frame)
+    0x05,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(GG_UINT64_C(0x01020304),
+            visitor_.blocked_frame_.stream_id);
+
+  // Now test framing boundaries.
+  for (size_t i = kQuicFrameTypeSize; i < QuicFramer::GetBlockedFrameSize();
+       ++i) {
+    string expected_error = "Unable to read stream_id.";
+    CheckProcessingFails(
+        packet,
+        i + GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+        expected_error, QUIC_INVALID_BLOCKED_DATA);
+  }
+}
+
+TEST_P(QuicFramerTest, PingFrame) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (ping frame)
+    0x07,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(1u, visitor_.ping_frames_.size());
+
+  // No need to check the PING frame boundaries because it has no payload.
+}
+
+TEST_P(QuicFramerTest, PublicResetPacket) {
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte connection_id)
+    0x0E,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (2) + padding
+    0x02, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_reset_packet_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.public_reset_packet_->public_header.connection_id);
+  EXPECT_TRUE(visitor_.public_reset_packet_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.public_reset_packet_->public_header.version_flag);
+  EXPECT_EQ(GG_UINT64_C(0xABCDEF0123456789),
+            visitor_.public_reset_packet_->nonce_proof);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.public_reset_packet_->rejected_sequence_number);
+  EXPECT_TRUE(
+      visitor_.public_reset_packet_->client_address.address().empty());
+
+  // Now test framing boundaries.
+  for (size_t i = 0; i < arraysize(packet); ++i) {
+    string expected_error;
+    DVLOG(1) << "iteration: " << i;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PACKET_HEADER);
+    } else if (i < kPublicResetPacketMessageTagOffset) {
+      expected_error = "Unable to read ConnectionId.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PACKET_HEADER);
+    } else {
+      expected_error = "Unable to read reset message.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PUBLIC_RST_PACKET);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, PublicResetPacketWithTrailingJunk) {
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte connection_id)
+    0x0E,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (2) + padding
+    0x02, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+    // trailing junk
+    'j', 'u', 'n', 'k',
+  };
+
+  string expected_error = "Unable to read reset message.";
+  CheckProcessingFails(packet, arraysize(packet), expected_error,
+                       QUIC_INVALID_PUBLIC_RST_PACKET);
+}
+
+TEST_P(QuicFramerTest, PublicResetPacketWithClientAddress) {
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte connection_id)
+    0x0E,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (3) + padding
+    0x03, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // tag kCADR
+    'C', 'A', 'D', 'R',
+    // end offset 24
+    0x18, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+    // client address: 4.31.198.44:443
+    0x02, 0x00,
+    0x04, 0x1F, 0xC6, 0x2C,
+    0xBB, 0x01,
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.public_reset_packet_.get());
+  EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+            visitor_.public_reset_packet_->public_header.connection_id);
+  EXPECT_TRUE(visitor_.public_reset_packet_->public_header.reset_flag);
+  EXPECT_FALSE(visitor_.public_reset_packet_->public_header.version_flag);
+  EXPECT_EQ(GG_UINT64_C(0xABCDEF0123456789),
+            visitor_.public_reset_packet_->nonce_proof);
+  EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+            visitor_.public_reset_packet_->rejected_sequence_number);
+  EXPECT_EQ("4.31.198.44",
+            IPAddressToString(visitor_.public_reset_packet_->
+                client_address.address()));
+  EXPECT_EQ(443, visitor_.public_reset_packet_->client_address.port());
+
+  // Now test framing boundaries.
+  for (size_t i = 0; i < arraysize(packet); ++i) {
+    string expected_error;
+    DVLOG(1) << "iteration: " << i;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PACKET_HEADER);
+    } else if (i < kPublicResetPacketMessageTagOffset) {
+      expected_error = "Unable to read ConnectionId.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PACKET_HEADER);
+    } else {
+      expected_error = "Unable to read reset message.";
+      CheckProcessingFails(packet, i, expected_error,
+                           QUIC_INVALID_PUBLIC_RST_PACKET);
+    }
+  }
+}
+
+TEST_P(QuicFramerTest, VersionNegotiationPacket) {
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x3D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+    'Q', '2', '.', '0',
+  };
+
+  QuicFramerPeer::SetIsServer(&framer_, false);
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.version_negotiation_packet_.get());
+  EXPECT_EQ(2u, visitor_.version_negotiation_packet_->versions.size());
+  EXPECT_EQ(GetParam(), visitor_.version_negotiation_packet_->versions[0]);
+
+  for (size_t i = 0; i <= kPublicFlagsSize + PACKET_8BYTE_CONNECTION_ID; ++i) {
+    string expected_error;
+    QuicErrorCode error_code = QUIC_INVALID_PACKET_HEADER;
+    if (i < kConnectionIdOffset) {
+      expected_error = "Unable to read public flags.";
+    } else if (i < kVersionOffset) {
+      expected_error = "Unable to read ConnectionId.";
+    } else {
+      expected_error = "Unable to read supported version in negotiation.";
+      error_code = QUIC_INVALID_VERSION_NEGOTIATION_PACKET;
+    }
+    CheckProcessingFails(packet, i, expected_error, error_code);
+  }
+}
+
+TEST_P(QuicFramerTest, FecPacket) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (fec group & FEC)
+    0x06,
+    // first fec protected packet offset
+    0x01,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+  EXPECT_EQ(0u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+  ASSERT_EQ(1, visitor_.fec_count_);
+  const QuicFecData& fec_data = *visitor_.fec_data_[0];
+  EXPECT_EQ(GG_UINT64_C(0x0123456789ABB), fec_data.fec_group);
+  EXPECT_EQ("abcdefghijklmnop", fec_data.redundancy);
+}
+
+TEST_P(QuicFramerTest, BuildPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicPaddingFrame padding_frame;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&padding_frame));
+
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  uint64 header_size =
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                          PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+  memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build4ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.public_header.sequence_number_length = PACKET_4BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicPaddingFrame padding_frame;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&padding_frame));
+
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 4 byte sequence number)
+    0x2C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  uint64 header_size =
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                          PACKET_4BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+  memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build2ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.public_header.sequence_number_length = PACKET_2BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicPaddingFrame padding_frame;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&padding_frame));
+
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 2 byte sequence number)
+    0x1C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  uint64 header_size =
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                          PACKET_2BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+  memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build1ByteSequenceNumberPaddingFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicPaddingFrame padding_frame;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&padding_frame));
+
+  unsigned char packet[kMaxPacketSize] = {
+    // public flags (8 byte connection_id and 1 byte sequence number)
+    0x0C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC,
+    // private flags
+    0x00,
+
+    // frame type (padding frame)
+    0x00,
+    0x00, 0x00, 0x00, 0x00
+  };
+
+  uint64 header_size =
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                          PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+  memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x77123456789ABC);
+  header.fec_group = 0;
+
+  QuicStreamFrame stream_frame;
+  stream_frame.stream_id = 0x01020304;
+  stream_frame.fin = true;
+  stream_frame.offset = GG_UINT64_C(0xBA98FEDC32107654);
+  stream_frame.data = MakeIOVector("hello world!");
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&stream_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacketInFecGroup) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x77123456789ABC);
+  header.is_in_fec_group = IN_FEC_GROUP;
+  header.fec_group = GG_UINT64_C(0x77123456789ABC);
+
+  QuicStreamFrame stream_frame;
+  stream_frame.stream_id = 0x01020304;
+  stream_frame.fin = true;
+  stream_frame.offset = GG_UINT64_C(0xBA98FEDC32107654);
+  stream_frame.data = MakeIOVector("hello world!");
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&stream_frame));
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy, is_in_fec_group)
+    0x03,
+    // FEC group
+    0x00,
+    // frame type (stream frame with fin and data length field)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length (since packet is in an FEC group)
+    0x0C, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacketWithVersionFlag) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = true;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x77123456789ABC);
+  header.fec_group = 0;
+
+  QuicStreamFrame stream_frame;
+  stream_frame.stream_id = 0x01020304;
+  stream_frame.fin = true;
+  stream_frame.offset = GG_UINT64_C(0xBA98FEDC32107654);
+  stream_frame.data = MakeIOVector("hello world!");
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&stream_frame));
+
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x3D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicFramerPeer::SetIsServer(&framer_, false);
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildVersionNegotiationPacket) {
+  QuicPacketPublicHeader header;
+  header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.reset_flag = false;
+  header.version_flag = true;
+
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x0D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '0', GetQuicVersionDigitTens(), GetQuicVersionDigitOnes(),
+  };
+
+  QuicVersionVector versions;
+  versions.push_back(GetParam());
+  scoped_ptr<QuicEncryptedPacket> data(
+      framer_.BuildVersionNegotiationPacket(header, versions));
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacketv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = GG_UINT64_C(0x770123456789ABF);
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  ack_frame.missing_packets.insert(
+      GG_UINT64_C(0x770123456789ABE));
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0x43,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacket) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = GG_UINT64_C(0x770123456789ABF);
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  ack_frame.missing_packets.insert(
+      GG_UINT64_C(0x770123456789ABE));
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, not truncated, 6 byte largest observed, 1 byte delta)
+    0x6C,
+    // entropy hash of all received packets.
+    0x43,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Zero delta time.
+    0x0, 0x0,
+    // num received packets.
+    0x00,
+    // num missing packet ranges
+    0x01,
+    // missing packet delta
+    0x01,
+    // 0 more missing packets in range.
+    0x00,
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+// TODO(jri): Add test for tuncated packets in which the original ack frame had
+// revived packets. (In both the large and small packet cases below).
+
+TEST_P(QuicFramerTest, BuildTruncatedAckFrameLargePacketv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  // This entropy hash is different from what shows up in the packet below,
+  // since entropy is recomputed by the framer on ack truncation (by
+  // TestEntropyCalculator for this test.)
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = 2 * 300;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  for (size_t i = 1; i < 2 * 300; i += 2) {
+    ack_frame.missing_packets.insert(i);
+  }
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, is truncated, 2 byte largest observed, 1 byte delta)
+    0x74,
+    // entropy hash of all received packets, set to 1 by TestEntropyCalculator
+    // since ack is truncated.
+    0x01,
+    // 2-byte largest observed packet sequence number.
+    // Expected to be 510 (0x1FE), since only 255 nack ranges can fit.
+    0xFE, 0x01,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges (limited to 255 by size of this field).
+    0xFF,
+    // {missing packet delta, further missing packets in range}
+    // 6 nack ranges x 42 + 3 nack ranges
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(
+      framer_.BuildDataPacket(header, frames, kMaxPacketSize).packet);
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildTruncatedAckFrameLargePacket) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  // This entropy hash is different from what shows up in the packet below,
+  // since entropy is recomputed by the framer on ack truncation (by
+  // TestEntropyCalculator for this test.)
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = 2 * 300;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  for (size_t i = 1; i < 2 * 300; i += 2) {
+    ack_frame.missing_packets.insert(i);
+  }
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, is truncated, 2 byte largest observed, 1 byte delta)
+    0x74,
+    // entropy hash of all received packets, set to 1 by TestEntropyCalculator
+    // since ack is truncated.
+    0x01,
+    // 2-byte largest observed packet sequence number.
+    // Expected to be 510 (0x1FE), since only 255 nack ranges can fit.
+    0xFE, 0x01,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges (limited to 255 by size of this field).
+    0xFF,
+    // {missing packet delta, further missing packets in range}
+    // 6 nack ranges x 42 + 3 nack ranges
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(
+      framer_.BuildDataPacket(header, frames, kMaxPacketSize).packet);
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+
+TEST_P(QuicFramerTest, BuildTruncatedAckFrameSmallPacketv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  // This entropy hash is different from what shows up in the packet below,
+  // since entropy is recomputed by the framer on ack truncation (by
+  // TestEntropyCalculator for this test.)
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = 2 * 300;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  for (size_t i = 1; i < 2 * 300; i += 2) {
+    ack_frame.missing_packets.insert(i);
+  }
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, is truncated, 2 byte largest observed, 1 byte delta)
+    0x74,
+    // entropy hash of all received packets, set to 1 by TestEntropyCalculator
+    // since ack is truncated.
+    0x01,
+    // 2-byte largest observed packet sequence number.
+    // Expected to be 12 (0x0C), since only 6 nack ranges can fit.
+    0x0C, 0x00,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges (limited to 6 by packet size of 37).
+    0x06,
+    // {missing packet delta, further missing packets in range}
+    // 6 nack ranges
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(
+      framer_.BuildDataPacket(header, frames, 37u).packet);
+  ASSERT_TRUE(data != nullptr);
+  // Expect 1 byte unused since at least 2 bytes are needed to fit more nacks.
+  EXPECT_EQ(36u, data->length());
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildTruncatedAckFrameSmallPacket) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  // This entropy hash is different from what shows up in the packet below,
+  // since entropy is recomputed by the framer on ack truncation (by
+  // TestEntropyCalculator for this test.)
+  ack_frame.entropy_hash = 0x43;
+  ack_frame.largest_observed = 2 * 300;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  for (size_t i = 1; i < 2 * 300; i += 2) {
+    ack_frame.missing_packets.insert(i);
+  }
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (ack frame)
+    // (has nacks, is truncated, 2 byte largest observed, 1 byte delta)
+    0x74,
+    // entropy hash of all received packets, set to 1 by TestEntropyCalculator
+    // since ack is truncated.
+    0x01,
+    // 2-byte largest observed packet sequence number.
+    // Expected to be 12 (0x0C), since only 6 nack ranges can fit.
+    0x0C, 0x00,
+    // Zero delta time.
+    0x0, 0x0,
+    // num missing packet ranges (limited to 6 by packet size of 37).
+    0x06,
+    // {missing packet delta, further missing packets in range}
+    // 6 nack ranges
+    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
+    // 0 revived packets.
+    0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(
+      framer_.BuildDataPacket(header, frames, 37u).packet);
+  ASSERT_TRUE(data != nullptr);
+  // Expect 1 byte unused since at least 2 bytes are needed to fit more nacks.
+  EXPECT_EQ(36u, data->length());
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketTCP) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicCongestionFeedbackFrame congestion_feedback_frame;
+  congestion_feedback_frame.type = kTCP;
+  congestion_feedback_frame.tcp.receive_window = 0x4030;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&congestion_feedback_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (congestion feedback frame)
+    0x20,
+    // congestion feedback type (TCP)
+    0x00,
+    // TCP receive window
+    0x03, 0x04,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStopWaitingPacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x770123456789AA8);
+  header.fec_group = 0;
+
+  QuicStopWaitingFrame stop_waiting_frame;
+  stop_waiting_frame.entropy_hash = 0x14;
+  stop_waiting_frame.least_unacked = GG_UINT64_C(0x770123456789AA0);
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&stop_waiting_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xA8, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (stop waiting frame)
+    0x06,
+    // entropy hash of sent packets till least awaiting - 1.
+    0x14,
+    // least packet sequence number awaiting an ack, delta from sequence number.
+    0x08, 0x00, 0x00, 0x00,
+    0x00, 0x00,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketInvalidFeedback) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicCongestionFeedbackFrame congestion_feedback_frame;
+  congestion_feedback_frame.type =
+      static_cast<CongestionFeedbackType>(kTCP + 1);
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&congestion_feedback_frame));
+
+  scoped_ptr<QuicPacket> data;
+  EXPECT_DFATAL(
+      data.reset(BuildDataPacket(header, frames)),
+      "AppendCongestionFeedbackFrame failed");
+  ASSERT_TRUE(data == nullptr);
+}
+
+TEST_P(QuicFramerTest, BuildRstFramePacketQuic) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicRstStreamFrame rst_frame;
+  rst_frame.stream_id = 0x01020304;
+  rst_frame.error_code = static_cast<QuicRstStreamErrorCode>(0x05060708);
+  rst_frame.error_details = "because I can";
+  rst_frame.byte_offset = 0x0807060504030201;
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00,
+
+    // frame type (rst stream frame)
+    0x01,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // sent byte offset
+    0x01, 0x02, 0x03, 0x04,
+    0x05, 0x06, 0x07, 0x08,
+    // error code
+    0x08, 0x07, 0x06, 0x05,
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&rst_frame));
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCloseFramePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicConnectionCloseFrame close_frame;
+  close_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  close_frame.error_details = "because I can";
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&close_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy)
+    0x01,
+
+    // frame type (connection close frame)
+    0x02,
+    // error code
+    0x08, 0x07, 0x06, 0x05,
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildGoAwayPacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicGoAwayFrame goaway_frame;
+  goaway_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+  goaway_frame.last_good_stream_id = 0x01020304;
+  goaway_frame.reason_phrase = "because I can";
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&goaway_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags(entropy)
+    0x01,
+
+    // frame type (go away frame)
+    0x03,
+    // error code
+    0x08, 0x07, 0x06, 0x05,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // error details length
+    0x0d, 0x00,
+    // error details
+    'b',  'e',  'c',  'a',
+    'u',  's',  'e',  ' ',
+    'I',  ' ',  'c',  'a',
+    'n',
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildWindowUpdatePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicWindowUpdateFrame window_update_frame;
+  window_update_frame.stream_id = 0x01020304;
+  window_update_frame.byte_offset = 0x1122334455667788;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&window_update_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags(entropy)
+    0x01,
+
+    // frame type (window update frame)
+    0x04,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // byte offset
+    0x88, 0x77, 0x66, 0x55,
+    0x44, 0x33, 0x22, 0x11,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildBlockedPacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicBlockedFrame blocked_frame;
+  blocked_frame.stream_id = 0x01020304;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&blocked_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags(entropy)
+    0x01,
+
+    // frame type (blocked frame)
+    0x05,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+  };
+
+  scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                      data->length(), AsChars(packet),
+                                      arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPingPacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicPingFrame ping_frame;
+
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ping_frame));
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags(entropy)
+    0x01,
+
+    // frame type (ping frame)
+    0x07,
+  };
+
+  if (version_ >= QUIC_VERSION_18) {
+    scoped_ptr<QuicPacket> data(BuildDataPacket(header, frames));
+    ASSERT_TRUE(data != nullptr);
+
+    test::CompareCharArraysWithHexError("constructed packet", data->data(),
+                                        data->length(), AsChars(packet),
+                                        arraysize(packet));
+  } else {
+    string expected_error =
+        "Attempt to add a PingFrame in " + QuicVersionToString(version_);
+    EXPECT_DFATAL(BuildDataPacket(header, frames),
+                  expected_error);
+    return;
+  }
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacket) {
+  QuicPublicResetPacket reset_packet;
+  reset_packet.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  reset_packet.public_header.reset_flag = true;
+  reset_packet.public_header.version_flag = false;
+  reset_packet.rejected_sequence_number = GG_UINT64_C(0x123456789ABC);
+  reset_packet.nonce_proof = GG_UINT64_C(0xABCDEF0123456789);
+
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte ConnectionId)
+    0x0E,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (2) + padding
+    0x02, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+  };
+
+  scoped_ptr<QuicEncryptedPacket> data(
+      framer_.BuildPublicResetPacket(reset_packet));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacketWithClientAddress) {
+  QuicPublicResetPacket reset_packet;
+  reset_packet.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  reset_packet.public_header.reset_flag = true;
+  reset_packet.public_header.version_flag = false;
+  reset_packet.rejected_sequence_number = GG_UINT64_C(0x123456789ABC);
+  reset_packet.nonce_proof = GG_UINT64_C(0xABCDEF0123456789);
+  reset_packet.client_address = IPEndPoint(Loopback4(), 0x1234);
+
+  unsigned char packet[] = {
+    // public flags (public reset, 8 byte ConnectionId)
+    0x0E,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // message tag (kPRST)
+    'P', 'R', 'S', 'T',
+    // num_entries (3) + padding
+    0x03, 0x00, 0x00, 0x00,
+    // tag kRNON
+    'R', 'N', 'O', 'N',
+    // end offset 8
+    0x08, 0x00, 0x00, 0x00,
+    // tag kRSEQ
+    'R', 'S', 'E', 'Q',
+    // end offset 16
+    0x10, 0x00, 0x00, 0x00,
+    // tag kCADR
+    'C', 'A', 'D', 'R',
+    // end offset 24
+    0x18, 0x00, 0x00, 0x00,
+    // nonce proof
+    0x89, 0x67, 0x45, 0x23,
+    0x01, 0xEF, 0xCD, 0xAB,
+    // rejected sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12, 0x00, 0x00,
+    // client address
+    0x02, 0x00,
+    0x7F, 0x00, 0x00, 0x01,
+    0x34, 0x12,
+  };
+
+  scoped_ptr<QuicEncryptedPacket> data(
+      framer_.BuildPublicResetPacket(reset_packet));
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildFecPacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = true;
+  header.entropy_flag = true;
+  header.packet_sequence_number = (GG_UINT64_C(0x123456789ABC));
+  header.is_in_fec_group = IN_FEC_GROUP;
+  header.fec_group = GG_UINT64_C(0x123456789ABB);;
+
+  QuicFecData fec_data;
+  fec_data.fec_group = 1;
+  fec_data.redundancy = "abcdefghijklmnop";
+
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (entropy & fec group & fec packet)
+    0x07,
+    // first fec protected packet offset
+    0x01,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  scoped_ptr<QuicPacket> data(
+      framer_.BuildFecPacket(header, fec_data).packet);
+  ASSERT_TRUE(data != nullptr);
+
+  test::CompareCharArraysWithHexError("constructed packet",
+                                      data->data(), data->length(),
+                                      AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, EncryptPacket) {
+  QuicPacketSequenceNumber sequence_number = GG_UINT64_C(0x123456789ABC);
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (fec group & fec packet)
+    0x06,
+    // first fec protected packet offset
+    0x01,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  scoped_ptr<QuicPacket> raw(
+      QuicPacket::NewDataPacket(AsChars(packet), arraysize(packet), false,
+                                PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER));
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number, *raw));
+
+  ASSERT_TRUE(encrypted.get() != nullptr);
+  EXPECT_TRUE(CheckEncryption(sequence_number, raw.get()));
+}
+
+TEST_P(QuicFramerTest, EncryptPacketWithVersionFlag) {
+  QuicPacketSequenceNumber sequence_number = GG_UINT64_C(0x123456789ABC);
+  unsigned char packet[] = {
+    // public flags (version, 8 byte connection_id)
+    0x3D,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // version tag
+    'Q', '.', '1', '0',
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (fec group & fec flags)
+    0x06,
+    // first fec protected packet offset
+    0x01,
+
+    // redundancy
+    'a',  'b',  'c',  'd',
+    'e',  'f',  'g',  'h',
+    'i',  'j',  'k',  'l',
+    'm',  'n',  'o',  'p',
+  };
+
+  scoped_ptr<QuicPacket> raw(
+      QuicPacket::NewDataPacket(AsChars(packet), arraysize(packet), false,
+                                PACKET_8BYTE_CONNECTION_ID, kIncludeVersion,
+                                PACKET_6BYTE_SEQUENCE_NUMBER));
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number, *raw));
+
+  ASSERT_TRUE(encrypted.get() != nullptr);
+  EXPECT_TRUE(CheckEncryption(sequence_number, raw.get()));
+}
+
+TEST_P(QuicFramerTest, AckTruncationLargePacket) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  // Create a packet with just the ack.
+  QuicAckFrame ack_frame = MakeAckFrameWithNackRanges(300, 0u);
+  QuicFrame frame;
+  frame.type = ACK_FRAME;
+  frame.ack_frame = &ack_frame;
+  QuicFrames frames;
+  frames.push_back(frame);
+
+  // Build an ack packet with truncation due to limit in number of nack ranges.
+  scoped_ptr<QuicPacket> raw_ack_packet(
+      framer_.BuildDataPacket(header, frames, kMaxPacketSize).packet);
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  scoped_ptr<QuicEncryptedPacket> ack_packet(
+      framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+                            *raw_ack_packet));
+  // Now make sure we can turn our ack packet back into an ack frame.
+  ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0];
+  EXPECT_TRUE(processed_ack_frame.is_truncated);
+  EXPECT_EQ(510u, processed_ack_frame.largest_observed);
+  ASSERT_EQ(255u, processed_ack_frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      processed_ack_frame.missing_packets.begin();
+  EXPECT_EQ(1u, *missing_iter);
+  SequenceNumberSet::const_reverse_iterator last_missing_iter =
+      processed_ack_frame.missing_packets.rbegin();
+  EXPECT_EQ(509u, *last_missing_iter);
+}
+
+TEST_P(QuicFramerTest, AckTruncationSmallPacketv22) {
+  if (version_ > QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  // Create a packet with just the ack.
+  QuicAckFrame ack_frame = MakeAckFrameWithNackRanges(300, 0u);
+  QuicFrame frame;
+  frame.type = ACK_FRAME;
+  frame.ack_frame = &ack_frame;
+  QuicFrames frames;
+  frames.push_back(frame);
+
+  // Build an ack packet with truncation due to limit in number of nack ranges.
+  scoped_ptr<QuicPacket> raw_ack_packet(
+      framer_.BuildDataPacket(header, frames, 500).packet);
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  scoped_ptr<QuicEncryptedPacket> ack_packet(
+      framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+                            *raw_ack_packet));
+  // Now make sure we can turn our ack packet back into an ack frame.
+  ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0];
+  EXPECT_TRUE(processed_ack_frame.is_truncated);
+  EXPECT_EQ(476u, processed_ack_frame.largest_observed);
+  ASSERT_EQ(238u, processed_ack_frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      processed_ack_frame.missing_packets.begin();
+  EXPECT_EQ(1u, *missing_iter);
+  SequenceNumberSet::const_reverse_iterator last_missing_iter =
+      processed_ack_frame.missing_packets.rbegin();
+  EXPECT_EQ(475u, *last_missing_iter);
+}
+
+
+TEST_P(QuicFramerTest, AckTruncationSmallPacket) {
+  if (version_ <= QUIC_VERSION_22) {
+    return;
+  }
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = false;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  // Create a packet with just the ack.
+  QuicAckFrame ack_frame = MakeAckFrameWithNackRanges(300, 0u);
+  QuicFrame frame;
+  frame.type = ACK_FRAME;
+  frame.ack_frame = &ack_frame;
+  QuicFrames frames;
+  frames.push_back(frame);
+
+  // Build an ack packet with truncation due to limit in number of nack ranges.
+  scoped_ptr<QuicPacket> raw_ack_packet(
+      framer_.BuildDataPacket(header, frames, 500).packet);
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  scoped_ptr<QuicEncryptedPacket> ack_packet(
+      framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+                            *raw_ack_packet));
+  // Now make sure we can turn our ack packet back into an ack frame.
+  ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+  ASSERT_EQ(1u, visitor_.ack_frames_.size());
+  QuicAckFrame& processed_ack_frame = *visitor_.ack_frames_[0];
+  EXPECT_TRUE(processed_ack_frame.is_truncated);
+  EXPECT_EQ(476u, processed_ack_frame.largest_observed);
+  ASSERT_EQ(238u, processed_ack_frame.missing_packets.size());
+  SequenceNumberSet::const_iterator missing_iter =
+      processed_ack_frame.missing_packets.begin();
+  EXPECT_EQ(1u, *missing_iter);
+  SequenceNumberSet::const_reverse_iterator last_missing_iter =
+      processed_ack_frame.missing_packets.rbegin();
+  EXPECT_EQ(475u, *last_missing_iter);
+}
+
+TEST_P(QuicFramerTest, CleanTruncation) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = GG_UINT64_C(0xFEDCBA9876543210);
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.fec_flag = false;
+  header.entropy_flag = true;
+  header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+  header.fec_group = 0;
+
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 201;
+  for (uint64 i = 1; i < ack_frame.largest_observed; ++i) {
+    ack_frame.missing_packets.insert(i);
+  }
+
+  // Create a packet with just the ack.
+  QuicFrame frame;
+  frame.type = ACK_FRAME;
+  frame.ack_frame = &ack_frame;
+  QuicFrames frames;
+  frames.push_back(frame);
+
+  scoped_ptr<QuicPacket> raw_ack_packet(BuildDataPacket(header, frames));
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+
+  scoped_ptr<QuicEncryptedPacket> ack_packet(
+      framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+                            *raw_ack_packet));
+
+  // Now make sure we can turn our ack packet back into an ack frame.
+  ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+
+  // Test for clean truncation of the ack by comparing the length of the
+  // original packets to the re-serialized packets.
+  frames.clear();
+  frame.type = ACK_FRAME;
+  frame.ack_frame = visitor_.ack_frames_[0];
+  frames.push_back(frame);
+
+  size_t original_raw_length = raw_ack_packet->length();
+  raw_ack_packet.reset(BuildDataPacket(header, frames));
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+  EXPECT_EQ(original_raw_length, raw_ack_packet->length());
+  ASSERT_TRUE(raw_ack_packet != nullptr);
+}
+
+TEST_P(QuicFramerTest, EntropyFlagTest) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (Entropy)
+    0x01,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(1 << 4, visitor_.header_->entropy_hash);
+  EXPECT_FALSE(visitor_.header_->fec_flag);
+};
+
+TEST_P(QuicFramerTest, FecEntropyTest) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags (Entropy & fec group & FEC)
+    0x07,
+    // first fec protected packet offset
+    0xFF,
+
+    // frame type (stream frame with fin and no length)
+    0xDF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+  };
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+  ASSERT_TRUE(visitor_.header_.get());
+  EXPECT_TRUE(visitor_.header_->fec_flag);
+  EXPECT_TRUE(visitor_.header_->entropy_flag);
+  EXPECT_EQ(1 << 4, visitor_.header_->entropy_hash);
+};
+
+TEST_P(QuicFramerTest, StopPacketProcessing) {
+  unsigned char packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // Entropy
+    0x01,
+
+    // frame type (stream frame with fin)
+    0xFF,
+    // stream id
+    0x04, 0x03, 0x02, 0x01,
+    // offset
+    0x54, 0x76, 0x10, 0x32,
+    0xDC, 0xFE, 0x98, 0xBA,
+    // data length
+    0x0c, 0x00,
+    // data
+    'h',  'e',  'l',  'l',
+    'o',  ' ',  'w',  'o',
+    'r',  'l',  'd',  '!',
+
+    // frame type (ack frame)
+    0x40,
+    // entropy hash of sent packets till least awaiting - 1.
+    0x14,
+    // least packet sequence number awaiting an ack
+    0xA0, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // entropy hash of all received packets.
+    0x43,
+    // largest observed packet sequence number
+    0xBF, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // num missing packets
+    0x01,
+    // missing packet
+    0xBE, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+  };
+
+  MockFramerVisitor visitor;
+  framer_.set_visitor(&visitor);
+  EXPECT_CALL(visitor, OnPacket());
+  EXPECT_CALL(visitor, OnPacketHeader(_));
+  EXPECT_CALL(visitor, OnStreamFrame(_)).WillOnce(Return(false));
+  EXPECT_CALL(visitor, OnAckFrame(_)).Times(0);
+  EXPECT_CALL(visitor, OnPacketComplete());
+  EXPECT_CALL(visitor, OnUnauthenticatedPublicHeader(_)).WillOnce(Return(true));
+
+  QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+  EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+  EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_headers_stream.cc b/net/quic/quic_headers_stream.cc
new file mode 100644
index 0000000..35dac3f
--- /dev/null
+++ b/net/quic/quic_headers_stream.cc
@@ -0,0 +1,276 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_headers_stream.h"
+
+#include "net/quic/quic_session.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const QuicStreamId kInvalidStreamId = 0;
+
+}  // namespace
+
+// A SpdyFramer visitor which passed SYN_STREAM and SYN_REPLY frames to
+// the QuicDataStream, and closes the connection if any unexpected frames
+// are received.
+class QuicHeadersStream::SpdyFramerVisitor
+    : public SpdyFramerVisitorInterface,
+      public SpdyFramerDebugVisitorInterface {
+ public:
+  explicit SpdyFramerVisitor(QuicHeadersStream* stream) : stream_(stream) {}
+
+  // SpdyFramerVisitorInterface implementation
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           bool fin,
+                           bool unidirectional) OVERRIDE {
+    if (!stream_->IsConnected()) {
+      return;
+    }
+
+    if (associated_stream_id != 0) {
+      CloseConnection("associated_stream_id != 0");
+      return;
+    }
+
+    if (unidirectional != 0) {
+      CloseConnection("unidirectional != 0");
+      return;
+    }
+
+    stream_->OnSynStream(stream_id, priority, fin);
+  }
+
+  virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE {
+    if (!stream_->IsConnected()) {
+      return;
+    }
+
+    stream_->OnSynReply(stream_id, fin);
+  }
+
+  virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                        const char* header_data,
+                                        size_t len) OVERRIDE {
+    if (!stream_->IsConnected()) {
+      return false;
+    }
+    stream_->OnControlFrameHeaderData(stream_id, header_data, len);
+    return true;
+  }
+
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len,
+                                 bool fin) OVERRIDE {
+    if (fin && len == 0) {
+      // The framer invokes OnStreamFrameData with zero-length data and
+      // fin = true after processing a SYN_STREAM or SYN_REPLY frame
+      // that had the fin bit set.
+      return;
+    }
+    CloseConnection("SPDY DATA frame received.");
+  }
+
+  virtual void OnError(SpdyFramer* framer) OVERRIDE {
+    CloseConnection("SPDY framing error.");
+  }
+
+  virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+                                 size_t length,
+                                 bool fin) OVERRIDE {
+    CloseConnection("SPDY DATA frame received.");
+  }
+
+  virtual void OnRstStream(SpdyStreamId stream_id,
+                           SpdyRstStreamStatus status) OVERRIDE {
+    CloseConnection("SPDY RST_STREAM frame received.");
+  }
+
+  virtual void OnSetting(SpdySettingsIds id,
+                         uint8 flags,
+                         uint32 value) OVERRIDE {
+    CloseConnection("SPDY SETTINGS frame received.");
+  }
+
+  virtual void OnSettingsAck() OVERRIDE {
+    CloseConnection("SPDY SETTINGS frame received.");
+  }
+
+  virtual void OnSettingsEnd() OVERRIDE {
+    CloseConnection("SPDY SETTINGS frame received.");
+  }
+
+  virtual void OnPing(SpdyPingId unique_id, bool is_ack) OVERRIDE {
+    CloseConnection("SPDY PING frame received.");
+  }
+
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) OVERRIDE {
+    CloseConnection("SPDY GOAWAY frame received.");
+  }
+
+  virtual void OnHeaders(SpdyStreamId stream_id, bool fin, bool end) OVERRIDE {
+    CloseConnection("SPDY HEADERS frame received.");
+  }
+
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              uint32 delta_window_size) OVERRIDE {
+    CloseConnection("SPDY WINDOW_UPDATE frame received.");
+  }
+
+  virtual void OnPushPromise(SpdyStreamId stream_id,
+                             SpdyStreamId promised_stream_id,
+                             bool end) OVERRIDE {
+    LOG(DFATAL) << "PUSH_PROMISE frame received from a SPDY/3 framer";
+    CloseConnection("SPDY PUSH_PROMISE frame received.");
+  }
+
+  virtual void OnContinuation(SpdyStreamId stream_id, bool end) OVERRIDE {
+    CloseConnection("SPDY CONTINUATION frame received.");
+  }
+
+  virtual bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) OVERRIDE {
+    CloseConnection("SPDY unknown frame received.");
+    return false;
+  }
+
+  // SpdyFramerDebugVisitorInterface implementation
+  virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+                                     SpdyFrameType type,
+                                     size_t payload_len,
+                                     size_t frame_len) OVERRIDE {}
+
+  virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+                                        SpdyFrameType type,
+                                        size_t frame_len) OVERRIDE {
+    if (stream_->IsConnected()) {
+      stream_->OnCompressedFrameSize(frame_len);
+    }
+  }
+
+ private:
+  void CloseConnection(const string& details) {
+    if (stream_->IsConnected()) {
+      stream_->CloseConnectionWithDetails(
+          QUIC_INVALID_HEADERS_STREAM_DATA, details);
+    }
+  }
+
+ private:
+  QuicHeadersStream* stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyFramerVisitor);
+};
+
+QuicHeadersStream::QuicHeadersStream(QuicSession* session)
+    : ReliableQuicStream(kHeadersStreamId, session),
+      stream_id_(kInvalidStreamId),
+      fin_(false),
+      frame_len_(0),
+      spdy_framer_(SPDY3),
+      spdy_framer_visitor_(new SpdyFramerVisitor(this)) {
+  spdy_framer_.set_visitor(spdy_framer_visitor_.get());
+  spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+  if (version() < QUIC_VERSION_21) {
+    // Prior to QUIC_VERSION_21 the headers stream is not subject to any flow
+    // control.
+    DisableFlowControl();
+  }
+  // The headers stream is exempt from connection level flow control.
+  DisableConnectionFlowControlForThisStream();
+}
+
+QuicHeadersStream::~QuicHeadersStream() {}
+
+size_t QuicHeadersStream::WriteHeaders(
+    QuicStreamId stream_id,
+    const SpdyHeaderBlock& headers,
+    bool fin,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  scoped_ptr<SpdySerializedFrame> frame;
+  if (session()->is_server()) {
+    SpdySynReplyIR syn_reply(stream_id);
+    syn_reply.set_name_value_block(headers);
+    syn_reply.set_fin(fin);
+    frame.reset(spdy_framer_.SerializeFrame(syn_reply));
+  } else {
+    SpdySynStreamIR syn_stream(stream_id);
+    syn_stream.set_name_value_block(headers);
+    syn_stream.set_fin(fin);
+    frame.reset(spdy_framer_.SerializeFrame(syn_stream));
+  }
+  WriteOrBufferData(StringPiece(frame->data(), frame->size()), false,
+                    ack_notifier_delegate);
+  return frame->size();
+}
+
+uint32 QuicHeadersStream::ProcessRawData(const char* data,
+                                         uint32 data_len) {
+  return spdy_framer_.ProcessInput(data, data_len);
+}
+
+QuicPriority QuicHeadersStream::EffectivePriority() const { return 0; }
+
+void QuicHeadersStream::OnSynStream(SpdyStreamId stream_id,
+                                    SpdyPriority priority,
+                                    bool fin) {
+  if (!session()->is_server()) {
+    CloseConnectionWithDetails(
+        QUIC_INVALID_HEADERS_STREAM_DATA,
+        "SPDY SYN_STREAM frame received at the client");
+    return;
+  }
+  DCHECK_EQ(kInvalidStreamId, stream_id_);
+  stream_id_ = stream_id;
+  fin_ = fin;
+  session()->OnStreamHeadersPriority(stream_id, priority);
+}
+
+void QuicHeadersStream::OnSynReply(SpdyStreamId stream_id, bool fin) {
+  if (session()->is_server()) {
+    CloseConnectionWithDetails(
+        QUIC_INVALID_HEADERS_STREAM_DATA,
+        "SPDY SYN_REPLY frame received at the server");
+    return;
+  }
+  DCHECK_EQ(kInvalidStreamId, stream_id_);
+  stream_id_ = stream_id;
+  fin_ = fin;
+}
+
+void QuicHeadersStream::OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                                 const char* header_data,
+                                                 size_t len) {
+  DCHECK_EQ(stream_id_, stream_id);
+  if (len == 0) {
+    DCHECK_NE(0u, stream_id_);
+    DCHECK_NE(0u, frame_len_);
+    session()->OnStreamHeadersComplete(stream_id_, fin_, frame_len_);
+    // Reset state for the next frame.
+    stream_id_ = kInvalidStreamId;
+    fin_ = false;
+    frame_len_ = 0;
+  } else {
+    session()->OnStreamHeaders(stream_id_, StringPiece(header_data, len));
+  }
+}
+
+void QuicHeadersStream::OnCompressedFrameSize(size_t frame_len) {
+  DCHECK_EQ(kInvalidStreamId, stream_id_);
+  DCHECK_EQ(0u, frame_len_);
+  frame_len_ = frame_len;
+}
+
+bool QuicHeadersStream::IsConnected() {
+  return session()->connection()->connected();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_headers_stream.h b/net/quic/quic_headers_stream.h
new file mode 100644
index 0000000..c3ccbda
--- /dev/null
+++ b/net/quic/quic_headers_stream.h
@@ -0,0 +1,82 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_HEADERS_STREAM_H_
+#define NET_QUIC_QUIC_HEADERS_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+// Headers in QUIC are sent as SPDY SYN_STREAM or SYN_REPLY frames
+// over a reserved reliable stream with the id 2.  Each endpoint (client
+// and server) will allocate an instance of QuicHeadersStream to send
+// and receive headers.
+class NET_EXPORT_PRIVATE QuicHeadersStream : public ReliableQuicStream {
+ public:
+  explicit QuicHeadersStream(QuicSession* session);
+  virtual ~QuicHeadersStream();
+
+  // Writes |headers| for |stream_id| in a SYN_STREAM or SYN_REPLY
+  // frame to the peer.  If |fin| is true, the fin flag will be set on
+  // the SPDY frame.  Returns the size, in bytes, of the resulting
+  // SPDY frame.
+  size_t WriteHeaders(
+      QuicStreamId stream_id,
+      const SpdyHeaderBlock& headers,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // ReliableQuicStream implementation
+  virtual uint32 ProcessRawData(const char* data, uint32 data_len) OVERRIDE;
+  virtual QuicPriority EffectivePriority() const OVERRIDE;
+
+ private:
+  class SpdyFramerVisitor;
+
+  // The following methods are called by the SimpleVisitor.
+
+  // Called when a SYN_STREAM frame has been received.
+  void OnSynStream(SpdyStreamId stream_id,
+                   SpdyPriority priority,
+                   bool fin);
+
+  // Called when a SYN_REPLY frame been received.
+  void OnSynReply(SpdyStreamId stream_id, bool fin);
+
+  // Called when a chunk of header data is available. This is called
+  // after OnSynStream, or OnSynReply.
+  // |stream_id| The stream receiving the header data.
+  // |header_data| A buffer containing the header data chunk received.
+  // |len| The length of the header data buffer. A length of zero indicates
+  //       that the header data block has been completely sent.
+  void OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                const char* header_data,
+                                size_t len);
+
+  // Called when the size of the compressed frame payload is available.
+  void OnCompressedFrameSize(size_t frame_len);
+
+  // Returns true if the session is still connected.
+  bool IsConnected();
+
+  // Data about the stream whose headers are being processed.
+  QuicStreamId stream_id_;
+  bool fin_;
+  size_t frame_len_;
+
+  SpdyFramer spdy_framer_;
+  scoped_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicHeadersStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_HEADERS_STREAM_H_
diff --git a/net/quic/quic_headers_stream_test.cc b/net/quic/quic_headers_stream_test.cc
new file mode 100644
index 0000000..32d22fe
--- /dev/null
+++ b/net/quic/quic_headers_stream_test.cc
@@ -0,0 +1,340 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_headers_stream.h"
+
+#include "net/quic/quic_utils.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using testing::Invoke;
+using testing::StrictMock;
+using testing::WithArgs;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+  MOCK_METHOD1(OnError, void(SpdyFramer* framer));
+  MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId stream_id,
+                                       size_t length,
+                                       bool fin));
+  MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
+                                       const char* data,
+                                       size_t len,
+                                       bool fin));
+  MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
+                                              const char* header_data,
+                                              size_t len));
+  MOCK_METHOD5(OnSynStream, void(SpdyStreamId stream_id,
+                                 SpdyStreamId associated_stream_id,
+                                 SpdyPriority priority,
+                                 bool fin,
+                                 bool unidirectional));
+  MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin));
+  MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
+                                 SpdyRstStreamStatus status));
+  MOCK_METHOD1(OnSettings, void(bool clear_persisted));
+  MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
+  MOCK_METHOD0(OnSettingsAck, void());
+  MOCK_METHOD0(OnSettingsEnd, void());
+  MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+  MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
+                              SpdyGoAwayStatus status));
+  MOCK_METHOD3(OnHeaders, void(SpdyStreamId stream_id, bool fin, bool end));
+  MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
+                                    uint32 delta_window_size));
+  MOCK_METHOD2(OnCredentialFrameData, bool(const char* credential_data,
+                                           size_t len));
+  MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id));
+  MOCK_METHOD3(OnPushPromise, void(SpdyStreamId stream_id,
+                                   SpdyStreamId promised_stream_id,
+                                   bool end));
+  MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end));
+  MOCK_METHOD6(OnAltSvc, void(SpdyStreamId stream_id,
+                              uint32 max_age,
+                              uint16 port,
+                              StringPiece protocol_id,
+                              StringPiece host,
+                              StringPiece origin));
+  MOCK_METHOD2(OnUnknownFrame, bool(SpdyStreamId stream_id, int frame_type));
+};
+
+class QuicHeadersStreamTest : public ::testing::TestWithParam<bool> {
+ public:
+  static QuicVersionVector GetVersions() {
+    QuicVersionVector versions;
+    versions.push_back(QuicVersionMax());
+    return versions;
+  }
+
+  QuicHeadersStreamTest()
+      : connection_(new StrictMock<MockConnection>(is_server(), GetVersions())),
+        session_(connection_),
+        headers_stream_(QuicSessionPeer::GetHeadersStream(&session_)),
+        body_("hello world"),
+        framer_(SPDY3) {
+    headers_[":version"]  = "HTTP/1.1";
+    headers_[":status"] = "200 Ok";
+    headers_["content-length"] = "11";
+    framer_.set_visitor(&visitor_);
+    EXPECT_EQ(QuicVersionMax(), session_.connection()->version());
+    EXPECT_TRUE(headers_stream_ != nullptr);
+  }
+
+  QuicConsumedData SaveIov(const IOVector& data) {
+    const iovec* iov = data.iovec();
+    int count = data.Capacity();
+    for (int i = 0 ; i < count; ++i) {
+      saved_data_.append(static_cast<char*>(iov[i].iov_base), iov[i].iov_len);
+    }
+    return QuicConsumedData(saved_data_.length(), false);
+  }
+
+  bool SaveHeaderData(const char* data, int len) {
+    saved_header_data_.append(data, len);
+    return true;
+  }
+
+  void SaveHeaderDataStringPiece(StringPiece data) {
+    saved_header_data_.append(data.data(), data.length());
+  }
+
+  void WriteHeadersAndExpectSynStream(QuicStreamId stream_id,
+                                      bool fin,
+                                      QuicPriority priority) {
+    WriteHeadersAndCheckData(stream_id, fin, priority, SYN_STREAM);
+  }
+
+  void WriteHeadersAndExpectSynReply(QuicStreamId stream_id,
+                                     bool fin) {
+    WriteHeadersAndCheckData(stream_id, fin, 0, SYN_REPLY);
+  }
+
+  void WriteHeadersAndCheckData(QuicStreamId stream_id,
+                                bool fin,
+                                QuicPriority priority,
+                                SpdyFrameType type) {
+    // Write the headers and capture the outgoing data
+    EXPECT_CALL(session_, WritevData(kHeadersStreamId, _, _, false, _, nullptr))
+        .WillOnce(WithArgs<1>(Invoke(this, &QuicHeadersStreamTest::SaveIov)));
+    headers_stream_->WriteHeaders(stream_id, headers_, fin, nullptr);
+
+    // Parse the outgoing data and check that it matches was was written.
+    if (type == SYN_STREAM) {
+      EXPECT_CALL(visitor_, OnSynStream(stream_id, kNoAssociatedStream, 0,
+                                        // priority,
+                                        fin, kNotUnidirectional));
+    } else {
+      EXPECT_CALL(visitor_, OnSynReply(stream_id, fin));
+    }
+    EXPECT_CALL(visitor_, OnControlFrameHeaderData(stream_id, _, _))
+        .WillRepeatedly(WithArgs<1, 2>(
+            Invoke(this, &QuicHeadersStreamTest::SaveHeaderData)));
+    if (fin) {
+      EXPECT_CALL(visitor_, OnStreamFrameData(stream_id, nullptr, 0, true));
+    }
+    framer_.ProcessInput(saved_data_.data(), saved_data_.length());
+    EXPECT_FALSE(framer_.HasError()) << framer_.error_code();
+
+    CheckHeaders();
+    saved_data_.clear();
+  }
+
+  void CheckHeaders() {
+    SpdyHeaderBlock headers;
+    EXPECT_EQ(saved_header_data_.length(),
+              framer_.ParseHeaderBlockInBuffer(saved_header_data_.data(),
+                                               saved_header_data_.length(),
+                                               &headers));
+    EXPECT_EQ(headers_, headers);
+    saved_header_data_.clear();
+  }
+
+  bool is_server() {
+    return GetParam();
+  }
+
+  void CloseConnection() {
+    QuicConnectionPeer::CloseConnection(connection_);
+  }
+
+  static const bool kNotUnidirectional = false;
+  static const bool kNoAssociatedStream = false;
+
+  StrictMock<MockConnection>* connection_;
+  StrictMock<MockSession> session_;
+  QuicHeadersStream* headers_stream_;
+  SpdyHeaderBlock headers_;
+  string body_;
+  string saved_data_;
+  string saved_header_data_;
+  SpdyFramer framer_;
+  StrictMock<MockVisitor> visitor_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicHeadersStreamTest, testing::Bool());
+
+TEST_P(QuicHeadersStreamTest, StreamId) {
+  EXPECT_EQ(3u, headers_stream_->id());
+}
+
+TEST_P(QuicHeadersStreamTest, EffectivePriority) {
+  EXPECT_EQ(0u, headers_stream_->EffectivePriority());
+}
+
+TEST_P(QuicHeadersStreamTest, WriteHeaders) {
+  for (QuicStreamId stream_id = kClientDataStreamId1;
+       stream_id < kClientDataStreamId3; stream_id += 2) {
+    for (int count = 0; count < 2; ++count) {
+      bool fin = (count == 0);
+      if (is_server()) {
+        WriteHeadersAndExpectSynReply(stream_id, fin);
+      } else {
+        for (QuicPriority priority = 0; priority < 7; ++priority) {
+          WriteHeadersAndExpectSynStream(stream_id, fin, priority);
+        }
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessRawData) {
+  for (QuicStreamId stream_id = kClientDataStreamId1;
+       stream_id < kClientDataStreamId3; stream_id += 2) {
+    for (int count = 0; count < 2; ++count) {
+      bool fin = (count == 0);
+      for (QuicPriority priority = 0; priority < 7; ++priority) {
+        // Replace with "WriteHeadersAndSaveData"
+        scoped_ptr<SpdySerializedFrame> frame;
+        if (is_server()) {
+          SpdySynStreamIR syn_stream(stream_id);
+          syn_stream.set_name_value_block(headers_);
+          syn_stream.set_fin(fin);
+          frame.reset(framer_.SerializeSynStream(syn_stream));
+          EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0));
+        } else {
+          SpdySynReplyIR syn_reply(stream_id);
+          syn_reply.set_name_value_block(headers_);
+          syn_reply.set_fin(fin);
+          frame.reset(framer_.SerializeSynReply(syn_reply));
+        }
+        EXPECT_CALL(session_, OnStreamHeaders(stream_id, _))
+            .WillRepeatedly(WithArgs<1>(
+                Invoke(this,
+                       &QuicHeadersStreamTest::SaveHeaderDataStringPiece)));
+        EXPECT_CALL(session_,
+                    OnStreamHeadersComplete(stream_id, fin, frame->size()));
+        headers_stream_->ProcessRawData(frame->data(), frame->size());
+
+        CheckHeaders();
+      }
+    }
+  }
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) {
+  SpdyDataIR data(2, "");
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                             "SPDY DATA frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) {
+  SpdyRstStreamIR data(2, RST_STREAM_PROTOCOL_ERROR, "");
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(
+                  QUIC_INVALID_HEADERS_STREAM_DATA,
+                  "SPDY RST_STREAM frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdySettingsFrame) {
+  SpdySettingsIR data;
+  data.AddSetting(SETTINGS_UPLOAD_BANDWIDTH, true, true, 0);
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(
+                  QUIC_INVALID_HEADERS_STREAM_DATA,
+                  "SPDY SETTINGS frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) {
+  SpdyPingIR data(1);
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                             "SPDY PING frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) {
+  SpdyGoAwayIR data(1, GOAWAY_PROTOCOL_ERROR, "go away");
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                             "SPDY GOAWAY frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyHeadersFrame) {
+  SpdyHeadersIR data(1);
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                             "SPDY HEADERS frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) {
+  SpdyWindowUpdateIR data(1, 1);
+  scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data));
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(
+                  QUIC_INVALID_HEADERS_STREAM_DATA,
+                  "SPDY WINDOW_UPDATE frame received."))
+      .WillOnce(InvokeWithoutArgs(this,
+                                  &QuicHeadersStreamTest::CloseConnection));
+  headers_stream_->ProcessRawData(frame->data(), frame->size());
+}
+
+TEST_P(QuicHeadersStreamTest, NoConnectionLevelFlowControl) {
+  if (connection_->version() < QUIC_VERSION_21) {
+    EXPECT_FALSE(headers_stream_->flow_controller()->IsEnabled());
+  } else {
+    EXPECT_TRUE(headers_stream_->flow_controller()->IsEnabled());
+  }
+  EXPECT_FALSE(ReliableQuicStreamPeer::StreamContributesToConnectionFlowControl(
+      headers_stream_));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc
new file mode 100644
index 0000000..4f8e4e4
--- /dev/null
+++ b/net/quic/quic_http_stream.cc
@@ -0,0 +1,565 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_http_stream.h"
+
+#include "base/callback_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_http_utils.h"
+#include "net/quic/quic_reliable_client_stream.h"
+#include "net/quic/quic_utils.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+
+static const size_t kHeaderBufInitialSize = 4096;
+
+QuicHttpStream::QuicHttpStream(const base::WeakPtr<QuicClientSession>& session)
+    : next_state_(STATE_NONE),
+      session_(session),
+      session_error_(OK),
+      was_handshake_confirmed_(session->IsCryptoHandshakeConfirmed()),
+      stream_(nullptr),
+      request_info_(nullptr),
+      request_body_stream_(nullptr),
+      priority_(MINIMUM_PRIORITY),
+      response_info_(nullptr),
+      response_status_(OK),
+      response_headers_received_(false),
+      read_buf_(new GrowableIOBuffer()),
+      closed_stream_received_bytes_(0),
+      user_buffer_len_(0),
+      weak_factory_(this) {
+  DCHECK(session_);
+  session_->AddObserver(this);
+}
+
+QuicHttpStream::~QuicHttpStream() {
+  Close(false);
+  if (session_)
+    session_->RemoveObserver(this);
+}
+
+int QuicHttpStream::InitializeStream(const HttpRequestInfo* request_info,
+                                     RequestPriority priority,
+                                     const BoundNetLog& stream_net_log,
+                                     const CompletionCallback& callback) {
+  DCHECK(!stream_);
+  if (!session_)
+    return was_handshake_confirmed_ ? ERR_CONNECTION_CLOSED :
+        ERR_QUIC_HANDSHAKE_FAILED;
+
+  if (request_info->url.SchemeIsSecure()) {
+    SSLInfo ssl_info;
+    bool secure_session =
+        session_->GetSSLInfo(&ssl_info) && ssl_info.cert.get();
+    UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.SecureResourceSecureSession",
+                          secure_session);
+    if (!secure_session)
+      return ERR_REQUEST_FOR_SECURE_RESOURCE_OVER_INSECURE_QUIC;
+  }
+
+  stream_net_log_ = stream_net_log;
+  request_info_ = request_info;
+  request_time_ = base::Time::Now();
+  priority_ = priority;
+
+  int rv = stream_request_.StartRequest(
+      session_, &stream_, base::Bind(&QuicHttpStream::OnStreamReady,
+                                     weak_factory_.GetWeakPtr()));
+  if (rv == ERR_IO_PENDING) {
+    callback_ = callback;
+  } else if (rv == OK) {
+    stream_->SetDelegate(this);
+  } else if (!was_handshake_confirmed_) {
+    rv = ERR_QUIC_HANDSHAKE_FAILED;
+  }
+
+  return rv;
+}
+
+void QuicHttpStream::OnStreamReady(int rv) {
+  DCHECK(rv == OK || !stream_);
+  if (rv == OK) {
+    stream_->SetDelegate(this);
+  } else if (!was_handshake_confirmed_) {
+    rv = ERR_QUIC_HANDSHAKE_FAILED;
+  }
+
+  ResetAndReturn(&callback_).Run(rv);
+}
+
+int QuicHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
+                                HttpResponseInfo* response,
+                                const CompletionCallback& callback) {
+  CHECK(!request_body_stream_);
+  CHECK(!response_info_);
+  CHECK(!callback.is_null());
+  CHECK(response);
+
+   if (!stream_) {
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  QuicPriority priority = ConvertRequestPriorityToQuicPriority(priority_);
+  stream_->set_priority(priority);
+  // Store the serialized request headers.
+  CreateSpdyHeadersFromHttpRequest(*request_info_, request_headers,
+                                   SPDY3, /*direct=*/true, &request_headers_);
+
+  // Store the request body.
+  request_body_stream_ = request_info_->upload_data_stream;
+  if (request_body_stream_) {
+    // TODO(rch): Can we be more precise about when to allocate
+    // raw_request_body_buf_. Removed the following check. DoReadRequestBody()
+    // was being called even if we didn't yet allocate raw_request_body_buf_.
+    //   && (request_body_stream_->size() ||
+    //       request_body_stream_->is_chunked()))
+    // Use 10 packets as the body buffer size to give enough space to
+    // help ensure we don't often send out partial packets.
+    raw_request_body_buf_ = new IOBufferWithSize(10 * kMaxPacketSize);
+    // The request body buffer is empty at first.
+    request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_.get(), 0);
+  }
+
+  // Store the response info.
+  response_info_ = response;
+
+  next_state_ = STATE_SEND_HEADERS;
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING)
+    callback_ = callback;
+
+  return rv > 0 ? OK : rv;
+}
+
+UploadProgress QuicHttpStream::GetUploadProgress() const {
+  if (!request_body_stream_)
+    return UploadProgress();
+
+  return UploadProgress(request_body_stream_->position(),
+                        request_body_stream_->size());
+}
+
+int QuicHttpStream::ReadResponseHeaders(const CompletionCallback& callback) {
+  CHECK(!callback.is_null());
+
+  if (stream_ == nullptr)
+    return response_status_;
+
+  // Check if we already have the response headers. If so, return synchronously.
+  if (response_headers_received_)
+    return OK;
+
+  // Still waiting for the response, return IO_PENDING.
+  CHECK(callback_.is_null());
+  callback_ = callback;
+  return ERR_IO_PENDING;
+}
+
+int QuicHttpStream::ReadResponseBody(
+    IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+  CHECK(buf);
+  CHECK(buf_len);
+  CHECK(!callback.is_null());
+
+  // If we have data buffered, complete the IO immediately.
+  if (!response_body_.empty()) {
+    int bytes_read = 0;
+    while (!response_body_.empty() && buf_len > 0) {
+      scoped_refptr<IOBufferWithSize> data = response_body_.front();
+      const int bytes_to_copy = std::min(buf_len, data->size());
+      memcpy(&(buf->data()[bytes_read]), data->data(), bytes_to_copy);
+      buf_len -= bytes_to_copy;
+      if (bytes_to_copy == data->size()) {
+        response_body_.pop_front();
+      } else {
+        const int bytes_remaining = data->size() - bytes_to_copy;
+        IOBufferWithSize* new_buffer = new IOBufferWithSize(bytes_remaining);
+        memcpy(new_buffer->data(), &(data->data()[bytes_to_copy]),
+               bytes_remaining);
+        response_body_.pop_front();
+        response_body_.push_front(make_scoped_refptr(new_buffer));
+      }
+      bytes_read += bytes_to_copy;
+    }
+    return bytes_read;
+  }
+
+  if (!stream_) {
+    // If the stream is already closed, there is no body to read.
+    return response_status_;
+  }
+
+  CHECK(callback_.is_null());
+  CHECK(!user_buffer_.get());
+  CHECK_EQ(0, user_buffer_len_);
+
+  callback_ = callback;
+  user_buffer_ = buf;
+  user_buffer_len_ = buf_len;
+  return ERR_IO_PENDING;
+}
+
+void QuicHttpStream::Close(bool not_reusable) {
+  // Note: the not_reusable flag has no meaning for SPDY streams.
+  if (stream_) {
+    closed_stream_received_bytes_ = stream_->stream_bytes_read();
+    stream_->SetDelegate(nullptr);
+    stream_->Reset(QUIC_STREAM_CANCELLED);
+    stream_ = nullptr;
+    response_status_ = was_handshake_confirmed_ ?
+        ERR_CONNECTION_CLOSED : ERR_QUIC_HANDSHAKE_FAILED;
+  }
+}
+
+HttpStream* QuicHttpStream::RenewStreamForAuth() {
+  return nullptr;
+}
+
+bool QuicHttpStream::IsResponseBodyComplete() const {
+  return next_state_ == STATE_OPEN && !stream_;
+}
+
+bool QuicHttpStream::CanFindEndOfResponse() const {
+  return true;
+}
+
+bool QuicHttpStream::IsConnectionReused() const {
+  // TODO(rch): do something smarter here.
+  return stream_ && stream_->id() > 1;
+}
+
+void QuicHttpStream::SetConnectionReused() {
+  // QUIC doesn't need an indicator here.
+}
+
+bool QuicHttpStream::IsConnectionReusable() const {
+  // QUIC streams aren't considered reusable.
+  return false;
+}
+
+int64 QuicHttpStream::GetTotalReceivedBytes() const {
+  if (stream_) {
+    return stream_->stream_bytes_read();
+  }
+
+  return closed_stream_received_bytes_;
+}
+
+bool QuicHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+  // TODO(mmenke):  Figure out what to do here.
+  return true;
+}
+
+void QuicHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+  DCHECK(stream_);
+  stream_->GetSSLInfo(ssl_info);
+}
+
+void QuicHttpStream::GetSSLCertRequestInfo(
+    SSLCertRequestInfo* cert_request_info) {
+  DCHECK(stream_);
+  NOTIMPLEMENTED();
+}
+
+bool QuicHttpStream::IsSpdyHttpStream() const {
+  return false;
+}
+
+void QuicHttpStream::Drain(HttpNetworkSession* session) {
+  Close(false);
+  delete this;
+}
+
+void QuicHttpStream::SetPriority(RequestPriority priority) {
+  priority_ = priority;
+}
+
+int QuicHttpStream::OnDataReceived(const char* data, int length) {
+  DCHECK_NE(0, length);
+  // Are we still reading the response headers.
+  if (!response_headers_received_) {
+    // Grow the read buffer if necessary.
+    if (read_buf_->RemainingCapacity() < length) {
+      size_t additional_capacity = length - read_buf_->RemainingCapacity();
+      if (additional_capacity < kHeaderBufInitialSize)
+        additional_capacity = kHeaderBufInitialSize;
+      read_buf_->SetCapacity(read_buf_->capacity() + additional_capacity);
+    }
+    memcpy(read_buf_->data(), data, length);
+    read_buf_->set_offset(read_buf_->offset() + length);
+    int rv = ParseResponseHeaders();
+    if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+      DoCallback(rv);
+    }
+    return OK;
+  }
+
+  if (callback_.is_null()) {
+    BufferResponseBody(data, length);
+    return OK;
+  }
+
+  if (length <= user_buffer_len_) {
+    memcpy(user_buffer_->data(), data, length);
+  } else {
+    memcpy(user_buffer_->data(), data, user_buffer_len_);
+    int delta = length - user_buffer_len_;
+    BufferResponseBody(data + user_buffer_len_, delta);
+    length = user_buffer_len_;
+  }
+
+  user_buffer_ = nullptr;
+  user_buffer_len_ = 0;
+  DoCallback(length);
+  return OK;
+}
+
+void QuicHttpStream::OnClose(QuicErrorCode error) {
+  if (error != QUIC_NO_ERROR) {
+    response_status_ = was_handshake_confirmed_ ?
+        ERR_QUIC_PROTOCOL_ERROR : ERR_QUIC_HANDSHAKE_FAILED;
+  } else if (!response_headers_received_) {
+    response_status_ = ERR_ABORTED;
+  }
+
+  closed_stream_received_bytes_ = stream_->stream_bytes_read();
+  stream_ = nullptr;
+  if (!callback_.is_null())
+    DoCallback(response_status_);
+}
+
+void QuicHttpStream::OnError(int error) {
+  stream_ = nullptr;
+  response_status_ = was_handshake_confirmed_ ?
+      error : ERR_QUIC_HANDSHAKE_FAILED;
+  if (!callback_.is_null())
+    DoCallback(response_status_);
+}
+
+bool QuicHttpStream::HasSendHeadersComplete() {
+  return next_state_ > STATE_SEND_HEADERS_COMPLETE;
+}
+
+void QuicHttpStream::OnCryptoHandshakeConfirmed() {
+  was_handshake_confirmed_ = true;
+}
+
+void QuicHttpStream::OnSessionClosed(int error) {
+  Close(false);
+  session_error_ = error;
+  session_.reset();
+}
+
+void QuicHttpStream::OnIOComplete(int rv) {
+  rv = DoLoop(rv);
+
+  if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+    DoCallback(rv);
+  }
+}
+
+void QuicHttpStream::DoCallback(int rv) {
+  CHECK_NE(rv, ERR_IO_PENDING);
+  CHECK(!callback_.is_null());
+
+  // The client callback can do anything, including destroying this class,
+  // so any pending callback must be issued after everything else is done.
+  base::ResetAndReturn(&callback_).Run(rv);
+}
+
+int QuicHttpStream::DoLoop(int rv) {
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_SEND_HEADERS:
+        CHECK_EQ(OK, rv);
+        rv = DoSendHeaders();
+        break;
+      case STATE_SEND_HEADERS_COMPLETE:
+        rv = DoSendHeadersComplete(rv);
+        break;
+      case STATE_READ_REQUEST_BODY:
+        CHECK_EQ(OK, rv);
+        rv = DoReadRequestBody();
+        break;
+      case STATE_READ_REQUEST_BODY_COMPLETE:
+        rv = DoReadRequestBodyComplete(rv);
+        break;
+      case STATE_SEND_BODY:
+        CHECK_EQ(OK, rv);
+        rv = DoSendBody();
+        break;
+      case STATE_SEND_BODY_COMPLETE:
+        rv = DoSendBodyComplete(rv);
+        break;
+      case STATE_OPEN:
+        CHECK_EQ(OK, rv);
+        break;
+      default:
+        NOTREACHED() << "next_state_: " << next_state_;
+        break;
+    }
+  } while (next_state_ != STATE_NONE && next_state_ != STATE_OPEN &&
+           rv != ERR_IO_PENDING);
+
+  return rv;
+}
+
+int QuicHttpStream::DoSendHeaders() {
+  if (!stream_)
+    return ERR_UNEXPECTED;
+
+  // Log the actual request with the URL Request's net log.
+  stream_net_log_.AddEvent(
+      NetLog::TYPE_HTTP_TRANSACTION_QUIC_SEND_REQUEST_HEADERS,
+      base::Bind(&QuicRequestNetLogCallback, stream_->id(), &request_headers_,
+                 priority_));
+  // Also log to the QuicSession's net log.
+  stream_->net_log().AddEvent(
+      NetLog::TYPE_QUIC_HTTP_STREAM_SEND_REQUEST_HEADERS,
+      base::Bind(&QuicRequestNetLogCallback, stream_->id(), &request_headers_,
+                 priority_));
+
+  bool has_upload_data = request_body_stream_ != nullptr;
+
+  next_state_ = STATE_SEND_HEADERS_COMPLETE;
+  int rv = stream_->WriteHeaders(request_headers_, !has_upload_data, nullptr);
+  request_headers_.clear();
+  return rv;
+}
+
+int QuicHttpStream::DoSendHeadersComplete(int rv) {
+  if (rv < 0)
+    return rv;
+
+  next_state_ = request_body_stream_ ?
+      STATE_READ_REQUEST_BODY : STATE_OPEN;
+
+  return OK;
+}
+
+int QuicHttpStream::DoReadRequestBody() {
+  next_state_ = STATE_READ_REQUEST_BODY_COMPLETE;
+  return request_body_stream_->Read(
+      raw_request_body_buf_.get(),
+      raw_request_body_buf_->size(),
+      base::Bind(&QuicHttpStream::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int QuicHttpStream::DoReadRequestBodyComplete(int rv) {
+  // |rv| is the result of read from the request body from the last call to
+  // DoSendBody().
+  if (rv < 0)
+    return rv;
+
+  request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_.get(), rv);
+  if (rv == 0) {  // Reached the end.
+    DCHECK(request_body_stream_->IsEOF());
+  }
+
+  next_state_ = STATE_SEND_BODY;
+  return OK;
+}
+
+int QuicHttpStream::DoSendBody() {
+  if (!stream_)
+    return ERR_UNEXPECTED;
+
+  CHECK(request_body_stream_);
+  CHECK(request_body_buf_.get());
+  const bool eof = request_body_stream_->IsEOF();
+  int len = request_body_buf_->BytesRemaining();
+  if (len > 0 || eof) {
+    next_state_ = STATE_SEND_BODY_COMPLETE;
+    base::StringPiece data(request_body_buf_->data(), len);
+    return stream_->WriteStreamData(
+        data, eof,
+        base::Bind(&QuicHttpStream::OnIOComplete, weak_factory_.GetWeakPtr()));
+  }
+
+  next_state_ = STATE_OPEN;
+  return OK;
+}
+
+int QuicHttpStream::DoSendBodyComplete(int rv) {
+  if (rv < 0)
+    return rv;
+
+  request_body_buf_->DidConsume(request_body_buf_->BytesRemaining());
+
+  if (!request_body_stream_->IsEOF()) {
+    next_state_ = STATE_READ_REQUEST_BODY;
+    return OK;
+  }
+
+  next_state_ = STATE_OPEN;
+  return OK;
+}
+
+int QuicHttpStream::ParseResponseHeaders() {
+  size_t read_buf_len = static_cast<size_t>(read_buf_->offset());
+  SpdyFramer framer(SPDY3);
+  SpdyHeaderBlock headers;
+  char* data = read_buf_->StartOfBuffer();
+  size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(),
+                                               &headers);
+
+  if (len == 0) {
+    return ERR_IO_PENDING;
+  }
+
+  // Save the remaining received data.
+  size_t delta = read_buf_len - len;
+  if (delta > 0) {
+    BufferResponseBody(data + len, delta);
+  }
+
+  // The URLRequest logs these headers, so only log to the QuicSession's
+  // net log.
+  stream_->net_log().AddEvent(
+      NetLog::TYPE_QUIC_HTTP_STREAM_READ_RESPONSE_HEADERS,
+      base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
+
+  if (!SpdyHeadersToHttpResponse(headers, SPDY3, response_info_)) {
+    DLOG(WARNING) << "Invalid headers";
+    return ERR_QUIC_PROTOCOL_ERROR;
+  }
+  // Put the peer's IP address and port into the response.
+  IPEndPoint address = stream_->GetPeerAddress();
+  response_info_->socket_address = HostPortPair::FromIPEndPoint(address);
+  response_info_->connection_info =
+      HttpResponseInfo::CONNECTION_INFO_QUIC1_SPDY3;
+  response_info_->vary_data
+      .Init(*request_info_, *response_info_->headers.get());
+  response_info_->was_npn_negotiated = true;
+  response_info_->npn_negotiated_protocol = "quic/1+spdy/3";
+  response_info_->response_time = base::Time::Now();
+  response_info_->request_time = request_time_;
+  response_headers_received_ = true;
+
+  return OK;
+}
+
+void QuicHttpStream::BufferResponseBody(const char* data, int length) {
+  if (length == 0)
+    return;
+  IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+  memcpy(io_buffer->data(), data, length);
+  response_body_.push_back(make_scoped_refptr(io_buffer));
+}
+
+}  // namespace net
diff --git a/net/quic/quic_http_stream.h b/net/quic/quic_http_stream.h
new file mode 100644
index 0000000..5c60822
--- /dev/null
+++ b/net/quic/quic_http_stream.h
@@ -0,0 +1,173 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_HTTP_STREAM_H_
+#define NET_QUIC_QUIC_HTTP_STREAM_H_
+
+#include <list>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_stream.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+namespace test {
+class QuicHttpStreamPeer;
+}  // namespace test
+
+// The QuicHttpStream is a QUIC-specific HttpStream subclass.  It holds a
+// non-owning pointer to a QuicReliableClientStream which it uses to
+// send and receive data.
+class NET_EXPORT_PRIVATE QuicHttpStream :
+      public QuicClientSession::Observer,
+      public QuicReliableClientStream::Delegate,
+      public HttpStream {
+ public:
+  explicit QuicHttpStream(const base::WeakPtr<QuicClientSession>& session);
+
+  virtual ~QuicHttpStream();
+
+  // HttpStream implementation.
+  virtual int InitializeStream(const HttpRequestInfo* request_info,
+                               RequestPriority priority,
+                               const BoundNetLog& net_log,
+                               const CompletionCallback& callback) OVERRIDE;
+  virtual int SendRequest(const HttpRequestHeaders& request_headers,
+                          HttpResponseInfo* response,
+                          const CompletionCallback& callback) OVERRIDE;
+  virtual UploadProgress GetUploadProgress() const OVERRIDE;
+  virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+  virtual int ReadResponseBody(IOBuffer* buf,
+                               int buf_len,
+                               const CompletionCallback& callback) OVERRIDE;
+  virtual void Close(bool not_reusable) OVERRIDE;
+  virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+  virtual bool IsResponseBodyComplete() const OVERRIDE;
+  virtual bool CanFindEndOfResponse() const OVERRIDE;
+  virtual bool IsConnectionReused() const OVERRIDE;
+  virtual void SetConnectionReused() OVERRIDE;
+  virtual bool IsConnectionReusable() const OVERRIDE;
+  virtual int64 GetTotalReceivedBytes() const OVERRIDE;
+  virtual bool GetLoadTimingInfo(
+      LoadTimingInfo* load_timing_info) const OVERRIDE;
+  virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+  virtual void GetSSLCertRequestInfo(
+      SSLCertRequestInfo* cert_request_info) OVERRIDE;
+  virtual bool IsSpdyHttpStream() const OVERRIDE;
+  virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+  virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+  // QuicReliableClientStream::Delegate implementation
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+  virtual void OnClose(QuicErrorCode error) OVERRIDE;
+  virtual void OnError(int error) OVERRIDE;
+  virtual bool HasSendHeadersComplete() OVERRIDE;
+
+  // QuicClientSession::Observer implementation
+  virtual void OnCryptoHandshakeConfirmed() OVERRIDE;
+  virtual void OnSessionClosed(int error) OVERRIDE;
+
+ private:
+  friend class test::QuicHttpStreamPeer;
+
+  enum State {
+    STATE_NONE,
+    STATE_SEND_HEADERS,
+    STATE_SEND_HEADERS_COMPLETE,
+    STATE_READ_REQUEST_BODY,
+    STATE_READ_REQUEST_BODY_COMPLETE,
+    STATE_SEND_BODY,
+    STATE_SEND_BODY_COMPLETE,
+    STATE_OPEN,
+  };
+
+  void OnStreamReady(int rv);
+  void OnIOComplete(int rv);
+  void DoCallback(int rv);
+
+  int DoLoop(int);
+  int DoSendHeaders();
+  int DoSendHeadersComplete(int rv);
+  int DoReadRequestBody();
+  int DoReadRequestBodyComplete(int rv);
+  int DoSendBody();
+  int DoSendBodyComplete(int rv);
+  int DoReadResponseHeaders();
+  int DoReadResponseHeadersComplete(int rv);
+
+  int ParseResponseHeaders();
+
+  void BufferResponseBody(const char* data, int length);
+
+  State next_state_;
+
+  base::WeakPtr<QuicClientSession> session_;
+  int session_error_;  // Error code from the connection shutdown.
+  bool was_handshake_confirmed_;  // True if the crypto handshake succeeded.
+  QuicClientSession::StreamRequest stream_request_;
+  QuicReliableClientStream* stream_;  // Non-owning.
+
+  // The following three fields are all owned by the caller and must
+  // outlive this object, according to the HttpStream contract.
+
+  // The request to send.
+  const HttpRequestInfo* request_info_;
+  // The request body to send, if any, owned by the caller.
+  UploadDataStream* request_body_stream_;
+  // Time the request was issued.
+  base::Time request_time_;
+  // The priority of the request.
+  RequestPriority priority_;
+  // |response_info_| is the HTTP response data object which is filled in
+  // when a the response headers are read.  It is not owned by this stream.
+  HttpResponseInfo* response_info_;
+  // Because response data is buffered, also buffer the response status if the
+  // stream is explicitly closed via OnError or OnClose with an error.
+  // Once all buffered data has been returned, this will be used as the final
+  // response.
+  int response_status_;
+
+  // Serialized request headers.
+  SpdyHeaderBlock request_headers_;
+
+  bool response_headers_received_;
+
+  // Serialized HTTP request.
+  std::string request_;
+
+  // Buffer into which response header data is read.
+  scoped_refptr<GrowableIOBuffer> read_buf_;
+
+  // We buffer the response body as it arrives asynchronously from the stream.
+  // TODO(rch): This is infinite buffering, which is bad.
+  std::list<scoped_refptr<IOBufferWithSize> > response_body_;
+
+  // Number of bytes received when the stream was closed.
+  int64 closed_stream_received_bytes_;
+
+  // The caller's callback to be used for asynchronous operations.
+  CompletionCallback callback_;
+
+  // Caller provided buffer for the ReadResponseBody() response.
+  scoped_refptr<IOBuffer> user_buffer_;
+  int user_buffer_len_;
+
+  // Temporary buffer used to read the request body from UploadDataStream.
+  scoped_refptr<IOBufferWithSize> raw_request_body_buf_;
+  // Wraps raw_request_body_buf_ to read the remaining data progressively.
+  scoped_refptr<DrainableIOBuffer> request_body_buf_;
+
+  BoundNetLog stream_net_log_;
+
+  base::WeakPtrFactory<QuicHttpStream> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicHttpStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_HTTP_STREAM_H_
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
new file mode 100644
index 0000000..313e7f5
--- /dev/null
+++ b/net/quic/quic_http_stream_test.cc
@@ -0,0 +1,778 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_http_stream.h"
+
+#include <vector>
+
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_server_info.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_default_packet_writer.h"
+#include "net/quic/quic_http_utils.h"
+#include "net/quic/quic_reliable_client_stream.h"
+#include "net/quic/quic_write_blocked_list.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_packet_maker.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/test_task_runner.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::Return;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kUploadData[] = "Really nifty data!";
+const char kServerHostname[] = "www.google.com";
+const uint16 kServerPort = 80;
+
+class TestQuicConnection : public QuicConnection {
+ public:
+  TestQuicConnection(const QuicVersionVector& versions,
+                     QuicConnectionId connection_id,
+                     IPEndPoint address,
+                     QuicConnectionHelper* helper,
+                     const QuicConnection::PacketWriterFactory& writer_factory)
+      : QuicConnection(connection_id,
+                       address,
+                       helper,
+                       writer_factory,
+                       true  /* owns_writer */,
+                       false  /* is_server */,
+                       versions) {
+  }
+
+  void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+    QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+  }
+
+  void SetReceiveAlgorithm(ReceiveAlgorithmInterface* receive_algorithm) {
+    QuicConnectionPeer::SetReceiveAlgorithm(this, receive_algorithm);
+  }
+};
+
+class TestReceiveAlgorithm : public ReceiveAlgorithmInterface {
+ public:
+  virtual bool GenerateCongestionFeedback(
+      QuicCongestionFeedbackFrame* /*congestion_feedback*/) {
+    return false;
+  }
+
+  MOCK_METHOD3(RecordIncomingPacket,
+               void(QuicByteCount, QuicPacketSequenceNumber, QuicTime));
+};
+
+// Subclass of QuicHttpStream that closes itself when the first piece of data
+// is received.
+class AutoClosingStream : public QuicHttpStream {
+ public:
+  explicit AutoClosingStream(const base::WeakPtr<QuicClientSession>& session)
+      : QuicHttpStream(session) {
+  }
+
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+    Close(false);
+    return OK;
+  }
+};
+
+class TestPacketWriterFactory : public QuicConnection::PacketWriterFactory {
+ public:
+  explicit TestPacketWriterFactory(DatagramClientSocket* socket)
+      : socket_(socket) {}
+  virtual ~TestPacketWriterFactory() {}
+
+  virtual QuicPacketWriter* Create(QuicConnection* connection) const OVERRIDE {
+    return new QuicDefaultPacketWriter(socket_);
+  }
+
+ private:
+  DatagramClientSocket* socket_;
+};
+
+}  // namespace
+
+class QuicHttpStreamPeer {
+ public:
+  static QuicReliableClientStream* GetQuicReliableClientStream(
+      QuicHttpStream* stream) {
+    return stream->stream_;
+  }
+};
+
+class QuicHttpStreamTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+  static const bool kFin = true;
+  static const bool kIncludeVersion = true;
+  static const bool kIncludeCongestionFeedback = true;
+
+  // Holds a packet to be written to the wire, and the IO mode that should
+  // be used by the mock socket when performing the write.
+  struct PacketToWrite {
+    PacketToWrite(IoMode mode, QuicEncryptedPacket* packet)
+        : mode(mode),
+          packet(packet) {
+    }
+    IoMode mode;
+    QuicEncryptedPacket* packet;
+  };
+
+  QuicHttpStreamTest()
+      : net_log_(BoundNetLog()),
+        use_closing_stream_(false),
+        read_buffer_(new IOBufferWithSize(4096)),
+        connection_id_(2),
+        stream_id_(kClientDataStreamId1),
+        maker_(GetParam(), connection_id_, &clock_),
+        random_generator_(0) {
+    IPAddressNumber ip;
+    CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+    peer_addr_ = IPEndPoint(ip, 443);
+    self_addr_ = IPEndPoint(ip, 8435);
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
+  }
+
+  ~QuicHttpStreamTest() {
+    session_->CloseSessionOnError(ERR_ABORTED);
+    for (size_t i = 0; i < writes_.size(); i++) {
+      delete writes_[i].packet;
+    }
+  }
+
+  // Adds a packet to the list of expected writes.
+  void AddWrite(scoped_ptr<QuicEncryptedPacket> packet) {
+    writes_.push_back(PacketToWrite(SYNCHRONOUS, packet.release()));
+  }
+
+  // Returns the packet to be written at position |pos|.
+  QuicEncryptedPacket* GetWrite(size_t pos) {
+    return writes_[pos].packet;
+  }
+
+  bool AtEof() {
+    return socket_data_->at_read_eof() && socket_data_->at_write_eof();
+  }
+
+  void ProcessPacket(scoped_ptr<QuicEncryptedPacket> packet) {
+    connection_->ProcessUdpPacket(self_addr_, peer_addr_, *packet);
+  }
+
+  // Configures the test fixture to use the list of expected writes.
+  void Initialize() {
+    mock_writes_.reset(new MockWrite[writes_.size()]);
+    for (size_t i = 0; i < writes_.size(); i++) {
+      mock_writes_[i] = MockWrite(writes_[i].mode,
+                                  writes_[i].packet->data(),
+                                  writes_[i].packet->length());
+    };
+
+    socket_data_.reset(new StaticSocketDataProvider(
+        nullptr, 0, mock_writes_.get(), writes_.size()));
+
+    MockUDPClientSocket* socket = new MockUDPClientSocket(socket_data_.get(),
+                                                          net_log_.net_log());
+    socket->Connect(peer_addr_);
+    runner_ = new TestTaskRunner(&clock_);
+    send_algorithm_ = new MockSendAlgorithm();
+    receive_algorithm_ = new TestReceiveAlgorithm();
+    EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _)).
+        Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, _, _, _)).WillRepeatedly(Return(true));
+    EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+        Return(QuicTime::Delta::Zero()));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow()).WillRepeatedly(
+        Return(kMaxPacketSize));
+    EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).
+        WillRepeatedly(Return(QuicTime::Delta::Zero()));
+    EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillRepeatedly(
+        Return(QuicBandwidth::Zero()));
+    EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(AnyNumber());
+    helper_.reset(new QuicConnectionHelper(runner_.get(), &clock_,
+                                           &random_generator_));
+    TestPacketWriterFactory writer_factory(socket);
+    connection_ = new TestQuicConnection(SupportedVersions(GetParam()),
+                                         connection_id_, peer_addr_,
+                                         helper_.get(), writer_factory);
+    connection_->set_visitor(&visitor_);
+    connection_->SetSendAlgorithm(send_algorithm_);
+    connection_->SetReceiveAlgorithm(receive_algorithm_);
+    crypto_config_.SetDefaults();
+    session_.reset(
+        new QuicClientSession(connection_,
+                              scoped_ptr<DatagramClientSocket>(socket),
+                              nullptr,
+                              &transport_security_state_,
+                              make_scoped_ptr((QuicServerInfo*)nullptr),
+                              DefaultQuicConfig(),
+                              base::MessageLoop::current()->
+                                  message_loop_proxy().get(),
+                              nullptr));
+    session_->InitializeSession(QuicServerId(kServerHostname, kServerPort,
+                                             false, PRIVACY_MODE_DISABLED),
+                                &crypto_config_,
+                                &crypto_client_stream_factory_);
+    session_->GetCryptoStream()->CryptoConnect();
+    EXPECT_TRUE(session_->IsCryptoHandshakeConfirmed());
+    stream_.reset(use_closing_stream_ ?
+                  new AutoClosingStream(session_->GetWeakPtr()) :
+                  new QuicHttpStream(session_->GetWeakPtr()));
+  }
+
+  void SetRequest(const std::string& method,
+                  const std::string& path,
+                  RequestPriority priority) {
+    request_headers_ = maker_.GetRequestHeaders(method, "http", path);
+  }
+
+  void SetResponse(const std::string& status, const std::string& body) {
+    response_headers_ = maker_.GetResponseHeaders(status);
+    response_data_ = body;
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructDataPacket(
+      QuicPacketSequenceNumber sequence_number,
+      bool should_include_version,
+      bool fin,
+      QuicStreamOffset offset,
+      base::StringPiece data) {
+    return maker_.MakeDataPacket(
+        sequence_number, stream_id_, should_include_version, fin, offset, data);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructRequestHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      bool fin) {
+    return maker_.MakeRequestHeadersPacket(
+        sequence_number, stream_id_, kIncludeVersion, fin, request_headers_);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructResponseHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      bool fin) {
+    return maker_.MakeResponseHeadersPacket(
+        sequence_number, stream_id_, !kIncludeVersion, fin, response_headers_);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructRstStreamPacket(
+      QuicPacketSequenceNumber sequence_number) {
+    return maker_.MakeRstPacket(
+        sequence_number, true, stream_id_,
+        AdjustErrorForVersion(QUIC_RST_FLOW_CONTROL_ACCOUNTING, GetParam()));
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructAckAndRstStreamPacket(
+      QuicPacketSequenceNumber sequence_number) {
+    return maker_.MakeAckAndRstPacket(
+        sequence_number, !kIncludeVersion, stream_id_, QUIC_STREAM_CANCELLED,
+        2, 1, !kIncludeCongestionFeedback);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructAckPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicPacketSequenceNumber largest_received,
+      QuicPacketSequenceNumber least_unacked) {
+    return maker_.MakeAckPacket(sequence_number, largest_received,
+                                least_unacked, !kIncludeCongestionFeedback);
+  }
+
+  BoundNetLog net_log_;
+  bool use_closing_stream_;
+  MockSendAlgorithm* send_algorithm_;
+  TestReceiveAlgorithm* receive_algorithm_;
+  scoped_refptr<TestTaskRunner> runner_;
+  scoped_ptr<MockWrite[]> mock_writes_;
+  MockClock clock_;
+  TestQuicConnection* connection_;
+  scoped_ptr<QuicConnectionHelper> helper_;
+  testing::StrictMock<MockConnectionVisitor> visitor_;
+  scoped_ptr<QuicHttpStream> stream_;
+  TransportSecurityState transport_security_state_;
+  scoped_ptr<QuicClientSession> session_;
+  QuicCryptoClientConfig crypto_config_;
+  TestCompletionCallback callback_;
+  HttpRequestInfo request_;
+  HttpRequestHeaders headers_;
+  HttpResponseInfo response_;
+  scoped_refptr<IOBufferWithSize> read_buffer_;
+  SpdyHeaderBlock request_headers_;
+  SpdyHeaderBlock response_headers_;
+  std::string request_data_;
+  std::string response_data_;
+
+ private:
+  const QuicConnectionId connection_id_;
+  const QuicStreamId stream_id_;
+  QuicTestPacketMaker maker_;
+  IPEndPoint self_addr_;
+  IPEndPoint peer_addr_;
+  MockRandom random_generator_;
+  MockCryptoClientStreamFactory crypto_client_stream_factory_;
+  scoped_ptr<StaticSocketDataProvider> socket_data_;
+  std::vector<PacketToWrite> writes_;
+};
+
+INSTANTIATE_TEST_CASE_P(Version, QuicHttpStreamTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicHttpStreamTest, RenewStreamForAuth) {
+  Initialize();
+  EXPECT_EQ(nullptr, stream_->RenewStreamForAuth());
+}
+
+TEST_P(QuicHttpStreamTest, CanFindEndOfResponse) {
+  Initialize();
+  EXPECT_TRUE(stream_->CanFindEndOfResponse());
+}
+
+TEST_P(QuicHttpStreamTest, IsConnectionReusable) {
+  Initialize();
+  EXPECT_FALSE(stream_->IsConnectionReusable());
+}
+
+TEST_P(QuicHttpStreamTest, GetRequest) {
+  SetRequest("GET", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, kFin));
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  // Ack the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->ReadResponseHeaders(callback_.callback()));
+
+  SetResponse("404 Not Found", std::string());
+  ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
+
+  // Now that the headers have been processed, the callback will return.
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(404, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+  EXPECT_FALSE(response_.response_time.is_null());
+  EXPECT_FALSE(response_.request_time.is_null());
+
+  // There is no body, so this should return immediately.
+  EXPECT_EQ(0, stream_->ReadResponseBody(read_buffer_.get(),
+                                         read_buffer_->size(),
+                                         callback_.callback()));
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+// Regression test for http://crbug.com/288128
+TEST_P(QuicHttpStreamTest, GetRequestLargeResponse) {
+  SetRequest("GET", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, kFin));
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  // Ack the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->ReadResponseHeaders(callback_.callback()));
+
+  SpdyHeaderBlock headers;
+  headers[":status"] = "200 OK";
+  headers[":version"] = "HTTP/1.1";
+  headers["content-type"] = "text/plain";
+  headers["big6"] = std::string(10000, 'x');  // Lots of x's.
+
+  std::string response = SpdyUtils::SerializeUncompressedHeaders(headers);
+  EXPECT_LT(4096u, response.length());
+  stream_->OnDataReceived(response.data(), response.length());
+  stream_->OnClose(QUIC_NO_ERROR);
+
+  // Now that the headers have been processed, the callback will return.
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(200, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+  // There is no body, so this should return immediately.
+  EXPECT_EQ(0, stream_->ReadResponseBody(read_buffer_.get(),
+                                         read_buffer_->size(),
+                                         callback_.callback()));
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+// Regression test for http://crbug.com/409101
+TEST_P(QuicHttpStreamTest, SessionClosedBeforeSendRequest) {
+  SetRequest("GET", "/", DEFAULT_PRIORITY);
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+
+  session_->connection()->CloseConnection(QUIC_NO_ERROR, true);
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED,
+            stream_->SendRequest(headers_, &response_,
+                                 callback_.callback()));
+}
+
+// Regression test for http://crbug.com/409871
+TEST_P(QuicHttpStreamTest, SessionClosedBeforeReadResponseHeaders) {
+  SetRequest("GET", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, kFin));
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  session_->connection()->CloseConnection(QUIC_NO_ERROR, true);
+
+  EXPECT_NE(OK, stream_->ReadResponseHeaders(callback_.callback()));
+}
+
+TEST_P(QuicHttpStreamTest, SendPostRequest) {
+  SetRequest("POST", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, !kFin));
+  AddWrite(ConstructDataPacket(2, kIncludeVersion, kFin, 0, kUploadData));
+  AddWrite(ConstructAckPacket(3, 3, 1));
+
+  Initialize();
+
+  ScopedVector<UploadElementReader> element_readers;
+  element_readers.push_back(
+      new UploadBytesElementReader(kUploadData, strlen(kUploadData)));
+  UploadDataStream upload_data_stream(element_readers.Pass(), 0);
+  request_.method = "POST";
+  request_.url = GURL("http://www.google.com/");
+  request_.upload_data_stream = &upload_data_stream;
+  ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  // Ack both packets in the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  // Send the response headers (but not the body).
+  SetResponse("200 OK", std::string());
+  ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
+
+  // Since the headers have already arrived, this should return immediately.
+  EXPECT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(200, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+  // Send the response body.
+  const char kResponseBody[] = "Hello world!";
+  ProcessPacket(ConstructDataPacket(3, false, kFin, 0, kResponseBody));
+  // Since the body has already arrived, this should return immediately.
+  EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
+            stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+                                      callback_.callback()));
+
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+TEST_P(QuicHttpStreamTest, SendChunkedPostRequest) {
+  SetRequest("POST", "/", DEFAULT_PRIORITY);
+  size_t chunk_size = strlen(kUploadData);
+  AddWrite(ConstructRequestHeadersPacket(1, !kFin));
+  AddWrite(ConstructDataPacket(2, kIncludeVersion, !kFin, 0, kUploadData));
+  AddWrite(ConstructDataPacket(3, kIncludeVersion, kFin, chunk_size,
+                               kUploadData));
+  AddWrite(ConstructAckPacket(4, 3, 1));
+  Initialize();
+
+  UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0);
+  upload_data_stream.AppendChunk(kUploadData, chunk_size, false);
+
+  request_.method = "POST";
+  request_.url = GURL("http://www.google.com/");
+  request_.upload_data_stream = &upload_data_stream;
+  ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+  ASSERT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  ASSERT_EQ(ERR_IO_PENDING, stream_->SendRequest(headers_, &response_,
+                                                 callback_.callback()));
+
+  upload_data_stream.AppendChunk(kUploadData, chunk_size, true);
+
+  // Ack both packets in the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  // Send the response headers (but not the body).
+  SetResponse("200 OK", std::string());
+  ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
+
+  // Since the headers have already arrived, this should return immediately.
+  ASSERT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(200, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+  // Send the response body.
+  const char kResponseBody[] = "Hello world!";
+  ProcessPacket(ConstructDataPacket(3, false, kFin, response_data_.length(),
+                                    kResponseBody));
+
+  // Since the body has already arrived, this should return immediately.
+  ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
+            stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+                                      callback_.callback()));
+
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+TEST_P(QuicHttpStreamTest, SendChunkedPostRequestWithFinalEmptyDataPacket) {
+  SetRequest("POST", "/", DEFAULT_PRIORITY);
+  size_t chunk_size = strlen(kUploadData);
+  AddWrite(ConstructRequestHeadersPacket(1, !kFin));
+  AddWrite(ConstructDataPacket(2, kIncludeVersion, !kFin, 0, kUploadData));
+  AddWrite(ConstructDataPacket(3, kIncludeVersion, kFin, chunk_size, ""));
+  AddWrite(ConstructAckPacket(4, 3, 1));
+  Initialize();
+
+  UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0);
+  upload_data_stream.AppendChunk(kUploadData, chunk_size, false);
+
+  request_.method = "POST";
+  request_.url = GURL("http://www.google.com/");
+  request_.upload_data_stream = &upload_data_stream;
+  ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+  ASSERT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  ASSERT_EQ(ERR_IO_PENDING, stream_->SendRequest(headers_, &response_,
+                                                 callback_.callback()));
+
+  upload_data_stream.AppendChunk(nullptr, 0, true);
+
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  // Send the response headers (but not the body).
+  SetResponse("200 OK", std::string());
+  ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
+
+  // Since the headers have already arrived, this should return immediately.
+  ASSERT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(200, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+  // Send the response body.
+  const char kResponseBody[] = "Hello world!";
+  ProcessPacket(ConstructDataPacket(3, false, kFin, response_data_.length(),
+                                    kResponseBody));
+
+  // Since the body has already arrived, this should return immediately.
+  ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
+            stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+                                      callback_.callback()));
+
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+TEST_P(QuicHttpStreamTest, SendChunkedPostRequestWithOneEmptyDataPacket) {
+  SetRequest("POST", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, !kFin));
+  AddWrite(ConstructDataPacket(2, kIncludeVersion, kFin, 0, ""));
+  AddWrite(ConstructAckPacket(3, 3, 1));
+  Initialize();
+
+  UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0);
+
+  request_.method = "POST";
+  request_.url = GURL("http://www.google.com/");
+  request_.upload_data_stream = &upload_data_stream;
+  ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+  ASSERT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  ASSERT_EQ(ERR_IO_PENDING, stream_->SendRequest(headers_, &response_,
+                                                 callback_.callback()));
+
+  upload_data_stream.AppendChunk(nullptr, 0, true);
+
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+
+  // Send the response headers (but not the body).
+  SetResponse("200 OK", std::string());
+  ProcessPacket(ConstructResponseHeadersPacket(2, !kFin));
+
+  // Since the headers have already arrived, this should return immediately.
+  ASSERT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+  ASSERT_TRUE(response_.headers.get());
+  EXPECT_EQ(200, response_.headers->response_code());
+  EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+  // Send the response body.
+  const char kResponseBody[] = "Hello world!";
+  ProcessPacket(ConstructDataPacket(3, false, kFin, response_data_.length(),
+                                    kResponseBody));
+
+  // Since the body has already arrived, this should return immediately.
+  ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
+            stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+                                      callback_.callback()));
+
+  EXPECT_TRUE(stream_->IsResponseBodyComplete());
+  EXPECT_TRUE(AtEof());
+}
+
+TEST_P(QuicHttpStreamTest, DestroyedEarly) {
+  SetRequest("GET", "/", DEFAULT_PRIORITY);
+  AddWrite(ConstructRequestHeadersPacket(1, kFin));
+  AddWrite(ConstructAckAndRstStreamPacket(2));
+  use_closing_stream_ = true;
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+                                          net_log_, callback_.callback()));
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  // Ack the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->ReadResponseHeaders(callback_.callback()));
+
+  // Send the response with a body.
+  SetResponse("404 OK", "hello world!");
+  // In the course of processing this packet, the QuicHttpStream close itself.
+  ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
+
+  EXPECT_TRUE(AtEof());
+}
+
+TEST_P(QuicHttpStreamTest, Priority) {
+  SetRequest("GET", "/", MEDIUM);
+  AddWrite(ConstructRequestHeadersPacket(1, kFin));
+  AddWrite(ConstructAckAndRstStreamPacket(2));
+  use_closing_stream_ = true;
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, MEDIUM,
+                                          net_log_, callback_.callback()));
+
+  // Check that priority is highest.
+  QuicReliableClientStream* reliable_stream =
+      QuicHttpStreamPeer::GetQuicReliableClientStream(stream_.get());
+  DCHECK(reliable_stream);
+  DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
+            reliable_stream->EffectivePriority());
+
+  EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+                                     callback_.callback()));
+
+  // Check that priority has now dropped back to MEDIUM.
+  DCHECK_EQ(MEDIUM, ConvertQuicPriorityToRequestPriority(
+      reliable_stream->EffectivePriority()));
+
+  // Ack the request.
+  ProcessPacket(ConstructAckPacket(1, 0, 0));
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->ReadResponseHeaders(callback_.callback()));
+
+  // Send the response with a body.
+  SetResponse("404 OK", "hello world!");
+  // In the course of processing this packet, the QuicHttpStream close itself.
+  ProcessPacket(ConstructResponseHeadersPacket(2, kFin));
+
+  EXPECT_TRUE(AtEof());
+}
+
+// Regression test for http://crbug.com/294870
+TEST_P(QuicHttpStreamTest, CheckPriorityWithNoDelegate) {
+  SetRequest("GET", "/", MEDIUM);
+  use_closing_stream_ = true;
+
+  AddWrite(ConstructRstStreamPacket(1));
+
+  Initialize();
+
+  request_.method = "GET";
+  request_.url = GURL("http://www.google.com/");
+
+  EXPECT_EQ(OK, stream_->InitializeStream(&request_, MEDIUM,
+                                          net_log_, callback_.callback()));
+
+  // Check that priority is highest.
+  QuicReliableClientStream* reliable_stream =
+      QuicHttpStreamPeer::GetQuicReliableClientStream(stream_.get());
+  DCHECK(reliable_stream);
+  QuicReliableClientStream::Delegate* delegate = reliable_stream->GetDelegate();
+  DCHECK(delegate);
+  DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
+            reliable_stream->EffectivePriority());
+
+  // Set Delegate to nullptr and make sure EffectivePriority returns highest
+  // priority.
+  reliable_stream->SetDelegate(nullptr);
+  DCHECK_EQ(QuicWriteBlockedList::kHighestPriority,
+            reliable_stream->EffectivePriority());
+  reliable_stream->SetDelegate(delegate);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_http_utils.cc b/net/quic/quic_http_utils.cc
new file mode 100644
index 0000000..2bad8a6
--- /dev/null
+++ b/net/quic/quic_http_utils.cc
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_http_utils.h"
+
+namespace net {
+
+QuicPriority ConvertRequestPriorityToQuicPriority(
+    const RequestPriority priority) {
+  DCHECK_GE(priority, MINIMUM_PRIORITY);
+  DCHECK_LE(priority, MAXIMUM_PRIORITY);
+  return static_cast<QuicPriority>(HIGHEST - priority);
+}
+
+NET_EXPORT_PRIVATE RequestPriority ConvertQuicPriorityToRequestPriority(
+    QuicPriority priority) {
+  // Handle invalid values gracefully.
+  return (priority >= 5) ?
+      IDLE : static_cast<RequestPriority>(HIGHEST - priority);
+}
+
+base::Value* QuicRequestNetLogCallback(
+    QuicStreamId stream_id,
+    const SpdyHeaderBlock* headers,
+    QuicPriority priority,
+    NetLog::LogLevel log_level) {
+  base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(
+      SpdyHeaderBlockNetLogCallback(headers, log_level));
+  dict->SetInteger("quic_priority", static_cast<int>(priority));
+  dict->SetInteger("quic_stream_id", static_cast<int>(stream_id));
+  return dict;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_http_utils.h b/net/quic/quic_http_utils.h
new file mode 100644
index 0000000..862b7c6
--- /dev/null
+++ b/net/quic/quic_http_utils.h
@@ -0,0 +1,32 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_HTTP_UTILS_H_
+#define NET_QUIC_QUIC_HTTP_UTILS_H_
+
+#include "base/values.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/quic/quic_protocol.h"
+#include "net/spdy/spdy_header_block.h"
+
+namespace net {
+
+NET_EXPORT_PRIVATE QuicPriority ConvertRequestPriorityToQuicPriority(
+    RequestPriority priority);
+
+NET_EXPORT_PRIVATE RequestPriority ConvertQuicPriorityToRequestPriority(
+    QuicPriority priority);
+
+// Converts a SpdyHeaderBlock and priority into NetLog event parameters.  Caller
+// takes ownership of returned value.
+NET_EXPORT base::Value* QuicRequestNetLogCallback(
+    QuicStreamId stream_id,
+    const SpdyHeaderBlock* headers,
+    QuicPriority priority,
+    NetLog::LogLevel log_level);
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_HTTP_UTILS_H_
diff --git a/net/quic/quic_http_utils_test.cc b/net/quic/quic_http_utils_test.cc
new file mode 100644
index 0000000..93b62e2
--- /dev/null
+++ b/net/quic/quic_http_utils_test.cc
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_http_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicHttpUtilsTest, ConvertRequestPriorityToQuicPriority) {
+  EXPECT_EQ(0u, ConvertRequestPriorityToQuicPriority(HIGHEST));
+  EXPECT_EQ(1u, ConvertRequestPriorityToQuicPriority(MEDIUM));
+  EXPECT_EQ(2u, ConvertRequestPriorityToQuicPriority(LOW));
+  EXPECT_EQ(3u, ConvertRequestPriorityToQuicPriority(LOWEST));
+  EXPECT_EQ(4u, ConvertRequestPriorityToQuicPriority(IDLE));
+}
+
+TEST(QuicHttpUtilsTest, ConvertQuicPriorityToRequestPriority) {
+  EXPECT_EQ(HIGHEST, ConvertQuicPriorityToRequestPriority(0));
+  EXPECT_EQ(MEDIUM, ConvertQuicPriorityToRequestPriority(1));
+  EXPECT_EQ(LOW, ConvertQuicPriorityToRequestPriority(2));
+  EXPECT_EQ(LOWEST, ConvertQuicPriorityToRequestPriority(3));
+  EXPECT_EQ(IDLE, ConvertQuicPriorityToRequestPriority(4));
+  // These are invalid values, but we should still handle them
+  // gracefully. TODO(rtenneti): should we test for all possible values of
+  // uint32?
+  for (int i = 5; i < kuint8max; ++i) {
+    EXPECT_EQ(IDLE, ConvertQuicPriorityToRequestPriority(i));
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_in_memory_cache.cc b/net/quic/quic_in_memory_cache.cc
new file mode 100644
index 0000000..31e346f
--- /dev/null
+++ b/net/quic/quic_in_memory_cache.cc
@@ -0,0 +1,183 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_in_memory_cache.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_util.h"
+#include "url/gurl.h"
+
+using base::FilePath;
+using base::StringPiece;
+using std::string;
+
+// Specifies the directory used during QuicInMemoryCache
+// construction to seed the cache. Cache directory can be
+// generated using `wget -p --save-headers <url>
+
+namespace net {
+
+FilePath::StringType g_quic_in_memory_cache_dir = FILE_PATH_LITERAL("");
+
+QuicInMemoryCache::Response::Response() : response_type_(REGULAR_RESPONSE) {
+}
+
+QuicInMemoryCache::Response::~Response() {
+}
+
+// static
+QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
+  return Singleton<QuicInMemoryCache>::get();
+}
+
+const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
+    const GURL& url) const {
+  ResponseMap::const_iterator it = responses_.find(GetKey(url));
+  if (it == responses_.end()) {
+    return nullptr;
+  }
+  return it->second;
+}
+
+void QuicInMemoryCache::AddSimpleResponse(StringPiece path,
+                                          StringPiece version,
+                                          StringPiece response_code,
+                                          StringPiece response_detail,
+                                          StringPiece body) {
+  GURL url("http://" + path.as_string());
+
+  string status_line = version.as_string() + " " +
+                       response_code.as_string() + " " +
+                       response_detail.as_string();
+
+  string header = "content-length: " +
+                  base::Uint64ToString(static_cast<uint64>(body.length()));
+
+  scoped_refptr<HttpResponseHeaders> response_headers =
+      new HttpResponseHeaders(status_line + '\0' + header + '\0' + '\0');
+
+  AddResponse(url, response_headers, body);
+}
+
+void QuicInMemoryCache::AddResponse(
+    const GURL& url,
+    scoped_refptr<HttpResponseHeaders> response_headers,
+    StringPiece response_body) {
+  string key = GetKey(url);
+  VLOG(1) << "Adding response for: " << key;
+  if (ContainsKey(responses_, key)) {
+    LOG(DFATAL) << "Response for given request already exists!";
+    return;
+  }
+  Response* new_response = new Response();
+  new_response->set_headers(response_headers);
+  new_response->set_body(response_body);
+  responses_[key] = new_response;
+}
+
+void QuicInMemoryCache::AddSpecialResponse(StringPiece path,
+                                           SpecialResponseType response_type) {
+  GURL url("http://" + path.as_string());
+
+  AddResponse(url, nullptr, string());
+  responses_[GetKey(url)]->response_type_ = response_type;
+}
+
+QuicInMemoryCache::QuicInMemoryCache() {
+  Initialize();
+}
+
+void QuicInMemoryCache::ResetForTests() {
+  STLDeleteValues(&responses_);
+  Initialize();
+}
+
+void QuicInMemoryCache::Initialize() {
+  // If there's no defined cache dir, we have no initialization to do.
+  if (g_quic_in_memory_cache_dir.size() == 0) {
+    VLOG(1) << "No cache directory found. Skipping initialization.";
+    return;
+  }
+  VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: "
+          << g_quic_in_memory_cache_dir;
+
+  FilePath directory(g_quic_in_memory_cache_dir);
+  base::FileEnumerator file_list(directory,
+                                 true,
+                                 base::FileEnumerator::FILES);
+
+  FilePath file = file_list.Next();
+  for (; !file.empty(); file = file_list.Next()) {
+    // Need to skip files in .svn directories
+    if (file.value().find(FILE_PATH_LITERAL("/.svn/")) != string::npos) {
+      continue;
+    }
+
+    string file_contents;
+    base::ReadFileToString(file, &file_contents);
+
+    if (file_contents.length() > INT_MAX) {
+      LOG(WARNING) << "File '" << file.value() << "' too large: "
+                   << file_contents.length();
+      continue;
+    }
+    int file_len = static_cast<int>(file_contents.length());
+
+    int headers_end = HttpUtil::LocateEndOfHeaders(file_contents.data(),
+                                                   file_len);
+    if (headers_end < 1) {
+      LOG(DFATAL) << "Headers invalid or empty, ignoring: " << file.value();
+      continue;
+    }
+
+    string raw_headers =
+        HttpUtil::AssembleRawHeaders(file_contents.data(), headers_end);
+
+    scoped_refptr<HttpResponseHeaders> response_headers =
+        new HttpResponseHeaders(raw_headers);
+
+    string base;
+    if (response_headers->GetNormalizedHeader("X-Original-Url", &base)) {
+      response_headers->RemoveHeader("X-Original-Url");
+      // Remove the protocol so we can add it below.
+      if (StartsWithASCII(base, "https://", false)) {
+        base = base.substr(8);
+      } else if (StartsWithASCII(base, "http://", false)) {
+        base = base.substr(7);
+      }
+    } else {
+      base = file.AsUTF8Unsafe();
+    }
+    if (base.length() == 0 || base[0] == '/') {
+      LOG(DFATAL) << "Invalid path, ignoring: " << base;
+      continue;
+    }
+    if (base[base.length() - 1] == ',') {
+      base = base.substr(0, base.length() - 1);
+    }
+
+    GURL url("http://" + base);
+
+    VLOG(1) << "Inserting '" << GetKey(url) << "' into QuicInMemoryCache.";
+
+    StringPiece body(file_contents.data() + headers_end,
+                     file_contents.size() - headers_end);
+
+    AddResponse(url, response_headers, body);
+  }
+}
+
+QuicInMemoryCache::~QuicInMemoryCache() {
+  STLDeleteValues(&responses_);
+}
+
+string QuicInMemoryCache::GetKey(const GURL& url) const {
+  // Take everything but the scheme portion of the URL.
+  return url.host() + url.PathForRequest();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_in_memory_cache.h b/net/quic/quic_in_memory_cache.h
new file mode 100644
index 0000000..ee3a9de
--- /dev/null
+++ b/net/quic/quic_in_memory_cache.h
@@ -0,0 +1,114 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_IN_MEMORY_CACHE_H_
+#define NET_QUIC_QUIC_IN_MEMORY_CACHE_H_
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/files/file_util.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_piece.h"
+#include "net/http/http_response_headers.h"
+
+template <typename T> struct DefaultSingletonTraits;
+class GURL;
+
+namespace net {
+
+extern base::FilePath::StringType g_quic_in_memory_cache_dir;
+
+namespace test {
+class QuicInMemoryCachePeer;
+}  // namespace
+
+class QuicServer;
+
+// In-memory cache for HTTP responses.
+// Reads from disk cache generated by:
+// `wget -p --save_headers <url>`
+class QuicInMemoryCache {
+ public:
+  enum SpecialResponseType {
+    REGULAR_RESPONSE,  // Send the headers and body like a server should.
+    CLOSE_CONNECTION,  // Close the connection (sending the close packet).
+    IGNORE_REQUEST,    // Do nothing, expect the client to time out.
+  };
+
+  // Container for response header/body pairs.
+  class Response {
+   public:
+    Response();
+    ~Response();
+
+    SpecialResponseType response_type() const { return response_type_; }
+    const HttpResponseHeaders& headers() const { return *headers_.get(); }
+    const base::StringPiece body() const { return base::StringPiece(body_); }
+
+   private:
+    friend class QuicInMemoryCache;
+
+    void set_headers(scoped_refptr<HttpResponseHeaders> headers) {
+      headers_ = headers;
+    }
+    void set_body(base::StringPiece body) {
+      body.CopyToString(&body_);
+    }
+
+    SpecialResponseType response_type_;
+    scoped_refptr<HttpResponseHeaders> headers_;
+    std::string body_;
+
+    DISALLOW_COPY_AND_ASSIGN(Response);
+  };
+
+  // Returns the singleton instance of the cache.
+  static QuicInMemoryCache* GetInstance();
+
+  // Retrieve a response from this cache for a given request.
+  // If no appropriate response exists, nullptr is returned.
+  // Currently, responses are selected based on request URI only.
+  const Response* GetResponse(const GURL& url) const;
+
+  // Adds a simple response to the cache.  The response headers will
+  // only contain the "content-length" header with the length of |body|.
+  void AddSimpleResponse(base::StringPiece path,
+                         base::StringPiece version,
+                         base::StringPiece response_code,
+                         base::StringPiece response_detail,
+                         base::StringPiece body);
+
+  // Add a response to the cache.
+  void AddResponse(const GURL& url,
+                   scoped_refptr<HttpResponseHeaders> response_headers,
+                   base::StringPiece response_body);
+
+  // Simulate a special behavior at a particular path.
+  void AddSpecialResponse(base::StringPiece path,
+                          SpecialResponseType response_type);
+
+ private:
+  typedef base::hash_map<std::string, Response*> ResponseMap;
+  friend struct DefaultSingletonTraits<QuicInMemoryCache>;
+  friend class test::QuicInMemoryCachePeer;
+
+  QuicInMemoryCache();
+  ~QuicInMemoryCache();
+
+  void ResetForTests();
+
+  void Initialize();
+
+  std::string GetKey(const GURL& url) const;
+
+  // Cached responses.
+  ResponseMap responses_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicInMemoryCache);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_IN_MEMORY_CACHE_H_
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
new file mode 100644
index 0000000..9d12a01
--- /dev/null
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -0,0 +1,1041 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction_test_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_service.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_http_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_packet_maker.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/mock_client_socket_pool_manager.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+// This is the expected return from a current server advertising QUIC.
+static const char kQuicAlternateProtocolHttpHeader[] =
+    "Alternate-Protocol: 80:quic\r\n\r\n";
+static const char kQuicAlternateProtocol50pctHttpHeader[] =
+    "Alternate-Protocol: 80:quic,p=.5\r\n\r\n";
+static const char kQuicAlternateProtocolHttpsHeader[] =
+    "Alternate-Protocol: 443:quic\r\n\r\n";
+
+}  // namespace
+
+namespace net {
+namespace test {
+
+// Helper class to encapsulate MockReads and MockWrites for QUIC.
+// Simplify ownership issues and the interaction with the MockSocketFactory.
+class MockQuicData {
+ public:
+  ~MockQuicData() {
+    STLDeleteElements(&packets_);
+  }
+
+  void AddRead(scoped_ptr<QuicEncryptedPacket> packet) {
+    reads_.push_back(MockRead(SYNCHRONOUS, packet->data(), packet->length(),
+                              sequence_number_++));
+    packets_.push_back(packet.release());
+  }
+
+  void AddRead(IoMode mode, int rv) {
+    reads_.push_back(MockRead(mode, rv));
+  }
+
+  void AddWrite(scoped_ptr<QuicEncryptedPacket> packet) {
+    writes_.push_back(MockWrite(SYNCHRONOUS, packet->data(), packet->length(),
+                                sequence_number_++));
+    packets_.push_back(packet.release());
+  }
+
+  void AddDelayedSocketDataToFactory(MockClientSocketFactory* factory,
+                                     size_t delay) {
+    MockRead* reads = reads_.empty() ? nullptr  : &reads_[0];
+    MockWrite* writes = writes_.empty() ? nullptr  : &writes_[0];
+    socket_data_.reset(new DelayedSocketData(
+        delay, reads, reads_.size(), writes, writes_.size()));
+    factory->AddSocketDataProvider(socket_data_.get());
+  }
+
+ private:
+  std::vector<QuicEncryptedPacket*> packets_;
+  std::vector<MockWrite> writes_;
+  std::vector<MockRead> reads_;
+  size_t sequence_number_;
+  scoped_ptr<SocketDataProvider> socket_data_;
+};
+
+class QuicNetworkTransactionTest
+    : public PlatformTest,
+      public ::testing::WithParamInterface<QuicVersion> {
+ protected:
+  QuicNetworkTransactionTest()
+      : clock_(new MockClock),
+        maker_(GetParam(), 0, clock_),
+        ssl_config_service_(new SSLConfigServiceDefaults),
+        proxy_service_(ProxyService::CreateDirect()),
+        auth_handler_factory_(
+            HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
+        random_generator_(0),
+        hanging_data_(nullptr, 0, nullptr, 0) {
+    request_.method = "GET";
+    request_.url = GURL("http://www.google.com/");
+    request_.load_flags = 0;
+    clock_->AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
+  }
+
+  virtual void SetUp() {
+    NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+    base::MessageLoop::current()->RunUntilIdle();
+  }
+
+  virtual void TearDown() {
+    NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+    // Empty the current queue.
+    base::MessageLoop::current()->RunUntilIdle();
+    PlatformTest::TearDown();
+    NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+    base::MessageLoop::current()->RunUntilIdle();
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructConnectionClosePacket(
+      QuicPacketSequenceNumber num) {
+    return maker_.MakeConnectionClosePacket(num);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructAckPacket(
+      QuicPacketSequenceNumber largest_received,
+      QuicPacketSequenceNumber least_unacked) {
+    return maker_.MakeAckPacket(2, largest_received, least_unacked, true);
+  }
+
+  SpdyHeaderBlock GetRequestHeaders(const std::string& method,
+                                    const std::string& scheme,
+                                    const std::string& path) {
+    return maker_.GetRequestHeaders(method, scheme, path);
+  }
+
+  SpdyHeaderBlock GetResponseHeaders(const std::string& status) {
+    return maker_.GetResponseHeaders(status);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructDataPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      QuicStreamOffset offset,
+      base::StringPiece data) {
+    return maker_.MakeDataPacket(
+        sequence_number, stream_id, should_include_version, fin, offset, data);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructRequestHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      const SpdyHeaderBlock& headers) {
+    return maker_.MakeRequestHeadersPacket(
+        sequence_number, stream_id, should_include_version, fin, headers);
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructResponseHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      const SpdyHeaderBlock& headers) {
+    return maker_.MakeResponseHeadersPacket(
+        sequence_number, stream_id, should_include_version, fin, headers);
+  }
+
+  void CreateSession() {
+    CreateSessionWithFactory(&socket_factory_, false);
+  }
+
+  void CreateSessionWithNextProtos() {
+    CreateSessionWithFactory(&socket_factory_, true);
+  }
+
+  // If |use_next_protos| is true, enables SPDY and QUIC.
+  void CreateSessionWithFactory(ClientSocketFactory* socket_factory,
+                                bool use_next_protos) {
+    params_.enable_quic = true;
+    params_.quic_clock = clock_;
+    params_.quic_random = &random_generator_;
+    params_.client_socket_factory = socket_factory;
+    params_.quic_crypto_client_stream_factory = &crypto_client_stream_factory_;
+    params_.host_resolver = &host_resolver_;
+    params_.cert_verifier = &cert_verifier_;
+    params_.transport_security_state = &transport_security_state_;
+    params_.proxy_service = proxy_service_.get();
+    params_.ssl_config_service = ssl_config_service_.get();
+    params_.http_auth_handler_factory = auth_handler_factory_.get();
+    params_.http_server_properties = http_server_properties.GetWeakPtr();
+    params_.quic_supported_versions = SupportedVersions(GetParam());
+
+    if (use_next_protos) {
+      params_.use_alternate_protocols = true;
+      params_.next_protos = NextProtosSpdy3();
+    }
+
+    session_ = new HttpNetworkSession(params_);
+    session_->quic_stream_factory()->set_require_confirmation(false);
+  }
+
+  void CheckWasQuicResponse(const scoped_ptr<HttpNetworkTransaction>& trans) {
+    const HttpResponseInfo* response = trans->GetResponseInfo();
+    ASSERT_TRUE(response != nullptr);
+    ASSERT_TRUE(response->headers.get() != nullptr);
+    EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+    EXPECT_TRUE(response->was_fetched_via_spdy);
+    EXPECT_TRUE(response->was_npn_negotiated);
+    EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_QUIC1_SPDY3,
+              response->connection_info);
+  }
+
+  void CheckWasHttpResponse(const scoped_ptr<HttpNetworkTransaction>& trans) {
+    const HttpResponseInfo* response = trans->GetResponseInfo();
+    ASSERT_TRUE(response != nullptr);
+    ASSERT_TRUE(response->headers.get() != nullptr);
+    EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+    EXPECT_FALSE(response->was_fetched_via_spdy);
+    EXPECT_FALSE(response->was_npn_negotiated);
+    EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP1,
+              response->connection_info);
+  }
+
+  void CheckResponseData(HttpNetworkTransaction* trans,
+                         const std::string& expected) {
+    std::string response_data;
+    ASSERT_EQ(OK, ReadTransaction(trans, &response_data));
+    EXPECT_EQ(expected, response_data);
+  }
+
+  void RunTransaction(HttpNetworkTransaction* trans) {
+    TestCompletionCallback callback;
+    int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    EXPECT_EQ(OK, callback.WaitForResult());
+  }
+
+  void SendRequestAndExpectHttpResponse(const std::string& expected) {
+    scoped_ptr<HttpNetworkTransaction> trans(
+        new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+    RunTransaction(trans.get());
+    CheckWasHttpResponse(trans);
+    CheckResponseData(trans.get(), expected);
+  }
+
+  void SendRequestAndExpectQuicResponse(const std::string& expected) {
+    scoped_ptr<HttpNetworkTransaction> trans(
+        new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+    RunTransaction(trans.get());
+    CheckWasQuicResponse(trans);
+    CheckResponseData(trans.get(), expected);
+  }
+
+  void AddQuicAlternateProtocolMapping(
+      MockCryptoClientStream::HandshakeMode handshake_mode) {
+    crypto_client_stream_factory_.set_handshake_mode(handshake_mode);
+    session_->http_server_properties()->SetAlternateProtocol(
+        HostPortPair::FromURL(request_.url), 80, QUIC, 1);
+  }
+
+  void ExpectBrokenAlternateProtocolMapping() {
+    ASSERT_TRUE(session_->http_server_properties()->HasAlternateProtocol(
+        HostPortPair::FromURL(request_.url)));
+    const AlternateProtocolInfo alternate =
+        session_->http_server_properties()->GetAlternateProtocol(
+            HostPortPair::FromURL(request_.url));
+    EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol);
+  }
+
+  void ExpectQuicAlternateProtocolMapping() {
+    ASSERT_TRUE(session_->http_server_properties()->HasAlternateProtocol(
+        HostPortPair::FromURL(request_.url)));
+    const AlternateProtocolInfo alternate =
+        session_->http_server_properties()->GetAlternateProtocol(
+            HostPortPair::FromURL(request_.url));
+    EXPECT_EQ(QUIC, alternate.protocol);
+  }
+
+  void AddHangingNonAlternateProtocolSocketData() {
+    MockConnect hanging_connect(SYNCHRONOUS, ERR_IO_PENDING);
+    hanging_data_.set_connect_data(hanging_connect);
+    socket_factory_.AddSocketDataProvider(&hanging_data_);
+  }
+
+  MockClock* clock_;  // Owned by QuicStreamFactory after CreateSession.
+  QuicTestPacketMaker maker_;
+  scoped_refptr<HttpNetworkSession> session_;
+  MockClientSocketFactory socket_factory_;
+  MockCryptoClientStreamFactory crypto_client_stream_factory_;
+  MockHostResolver host_resolver_;
+  MockCertVerifier cert_verifier_;
+  TransportSecurityState transport_security_state_;
+  scoped_refptr<SSLConfigServiceDefaults> ssl_config_service_;
+  scoped_ptr<ProxyService> proxy_service_;
+  scoped_ptr<HttpAuthHandlerFactory> auth_handler_factory_;
+  MockRandom random_generator_;
+  HttpServerPropertiesImpl http_server_properties;
+  HttpNetworkSession::Params params_;
+  HttpRequestInfo request_;
+  CapturingBoundNetLog net_log_;
+  StaticSocketDataProvider hanging_data_;
+};
+
+INSTANTIATE_TEST_CASE_P(Version, QuicNetworkTransactionTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicNetworkTransactionTest, ForceQuic) {
+  params_.origin_to_force_quic_on =
+      HostPortPair::FromString("www.google.com:80");
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  CreateSession();
+
+  SendRequestAndExpectQuicResponse("hello!");
+
+  // Check that the NetLog was filled reasonably.
+  net::CapturingNetLog::CapturedEntryList entries;
+  net_log_.GetEntries(&entries);
+  EXPECT_LT(0u, entries.size());
+
+  // Check that we logged a QUIC_SESSION_PACKET_RECEIVED.
+  int pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_QUIC_SESSION_PACKET_RECEIVED,
+      net::NetLog::PHASE_NONE);
+  EXPECT_LT(0, pos);
+
+  // ... and also a TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED.
+  pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED,
+      net::NetLog::PHASE_NONE);
+  EXPECT_LT(0, pos);
+
+  std::string packet_sequence_number;
+  ASSERT_TRUE(entries[pos].GetStringValue(
+      "packet_sequence_number", &packet_sequence_number));
+  EXPECT_EQ("1", packet_sequence_number);
+
+  // ... and also a QUIC_SESSION_STREAM_FRAME_RECEIVED.
+  pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_RECEIVED,
+      net::NetLog::PHASE_NONE);
+  EXPECT_LT(0, pos);
+
+  int log_stream_id;
+  ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &log_stream_id));
+  EXPECT_EQ(3, log_stream_id);
+}
+
+TEST_P(QuicNetworkTransactionTest, QuicProxy) {
+  proxy_service_.reset(
+      ProxyService::CreateFixedFromPacResult("QUIC myproxy:70"));
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // There is no need to set up an alternate protocol job, because
+  // no attempt will be made to speak to the proxy over TCP.
+
+  CreateSession();
+
+  SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_P(QuicNetworkTransactionTest, ForceQuicWithErrorConnecting) {
+  params_.origin_to_force_quic_on =
+      HostPortPair::FromString("www.google.com:80");
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddRead(ASYNC, ERR_SOCKET_NOT_CONNECTED);
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 0);
+
+  CreateSession();
+
+  scoped_ptr<HttpNetworkTransaction> trans(
+      new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+  TestCompletionCallback callback;
+  int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+}
+
+TEST_P(QuicNetworkTransactionTest, DoNotForceQuicForHttps) {
+  // Attempt to "force" quic on 443, which will not be honored.
+  params_.origin_to_force_quic_on =
+      HostPortPair::FromString("www.google.com:443");
+
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider data(http_reads, arraysize(http_reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&data);
+  SSLSocketDataProvider ssl(ASYNC, OK);
+  socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+  CreateSession();
+
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_P(QuicNetworkTransactionTest, UseAlternateProtocolForQuic) {
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead(kQuicAlternateProtocolHttpHeader),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  CreateSessionWithNextProtos();
+
+  SendRequestAndExpectHttpResponse("hello world");
+  SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_P(QuicNetworkTransactionTest, UseAlternateProtocolProbabilityForQuic) {
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead(kQuicAlternateProtocol50pctHttpHeader),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  params_.alternate_protocol_probability_threshold = .25;
+  CreateSessionWithNextProtos();
+
+  SendRequestAndExpectHttpResponse("hello world");
+  SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_P(QuicNetworkTransactionTest, DontUseAlternateProtocolProbabilityForQuic) {
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead(kQuicAlternateProtocol50pctHttpHeader),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  params_.alternate_protocol_probability_threshold = .75;
+  CreateSessionWithNextProtos();
+
+  SendRequestAndExpectHttpResponse("hello world");
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_P(QuicNetworkTransactionTest,
+       DontUseAlternateProtocolWithBadProbabilityForQuic) {
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead("Alternate-Protocol: 443:quic,p=2\r\n\r\n"),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  params_.alternate_protocol_probability_threshold = .75;
+  CreateSessionWithNextProtos();
+
+  SendRequestAndExpectHttpResponse("hello world");
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_P(QuicNetworkTransactionTest, UseAlternateProtocolForQuicForHttps) {
+  params_.origin_to_force_quic_on =
+      HostPortPair::FromString("www.google.com:443");
+
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead(kQuicAlternateProtocolHttpsHeader),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  CreateSessionWithNextProtos();
+
+  // TODO(rtenneti): Test QUIC over HTTPS, GetSSLInfo().
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_P(QuicNetworkTransactionTest, HungAlternateProtocol) {
+  crypto_client_stream_factory_.set_handshake_mode(
+      MockCryptoClientStream::COLD_START);
+
+  MockWrite http_writes[] = {
+    MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n"),
+    MockWrite(SYNCHRONOUS, 1, "Host: www.google.com\r\n"),
+    MockWrite(SYNCHRONOUS, 2, "Connection: keep-alive\r\n\r\n")
+  };
+
+  MockRead http_reads[] = {
+    MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+    MockRead(SYNCHRONOUS, 4, kQuicAlternateProtocolHttpHeader),
+    MockRead(SYNCHRONOUS, 5, "hello world"),
+    MockRead(SYNCHRONOUS, OK, 6)
+  };
+
+  DeterministicMockClientSocketFactory socket_factory;
+
+  DeterministicSocketData http_data(http_reads, arraysize(http_reads),
+                                    http_writes, arraysize(http_writes));
+  socket_factory.AddSocketDataProvider(&http_data);
+
+  // The QUIC transaction will not be allowed to complete.
+  MockWrite quic_writes[] = {
+    MockWrite(ASYNC, ERR_IO_PENDING, 0)
+  };
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, ERR_IO_PENDING, 1),
+  };
+  DeterministicSocketData quic_data(quic_reads, arraysize(quic_reads),
+                                    quic_writes, arraysize(quic_writes));
+  socket_factory.AddSocketDataProvider(&quic_data);
+
+  // The HTTP transaction will complete.
+  DeterministicSocketData http_data2(http_reads, arraysize(http_reads),
+                                     http_writes, arraysize(http_writes));
+  socket_factory.AddSocketDataProvider(&http_data2);
+
+  CreateSessionWithFactory(&socket_factory, true);
+
+  // Run the first request.
+  http_data.StopAfter(arraysize(http_reads) + arraysize(http_writes));
+  SendRequestAndExpectHttpResponse("hello world");
+  ASSERT_TRUE(http_data.at_read_eof());
+  ASSERT_TRUE(http_data.at_write_eof());
+
+  // Now run the second request in which the QUIC socket hangs,
+  // and verify the the transaction continues over HTTP.
+  http_data2.StopAfter(arraysize(http_reads) + arraysize(http_writes));
+  SendRequestAndExpectHttpResponse("hello world");
+
+  ASSERT_TRUE(http_data2.at_read_eof());
+  ASSERT_TRUE(http_data2.at_write_eof());
+  ASSERT_TRUE(!quic_data.at_read_eof());
+  ASSERT_TRUE(!quic_data.at_write_eof());
+}
+
+TEST_P(QuicNetworkTransactionTest, ZeroRTTWithHttpRace) {
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+  SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_P(QuicNetworkTransactionTest, ZeroRTTWithNoHttpRace) {
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // In order for a new QUIC session to be established via alternate-protocol
+  // without racing an HTTP connection, we need the host resolution to happen
+  // synchronously.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("www.google.com", "192.168.0.1", "");
+  HostResolver::RequestInfo info(HostPortPair("www.google.com", 80));
+  AddressList address;
+  host_resolver_.Resolve(info,
+                         DEFAULT_PRIORITY,
+                         &address,
+                         CompletionCallback(),
+                         nullptr,
+                         net_log_.bound());
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+  SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_P(QuicNetworkTransactionTest, ZeroRTTWithProxy) {
+  proxy_service_.reset(
+      ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+
+  // Since we are using a proxy, the QUIC job will not succeed.
+  MockWrite http_writes[] = {
+    MockWrite(SYNCHRONOUS, 0, "GET http://www.google.com/ HTTP/1.1\r\n"),
+    MockWrite(SYNCHRONOUS, 1, "Host: www.google.com\r\n"),
+    MockWrite(SYNCHRONOUS, 2, "Proxy-Connection: keep-alive\r\n\r\n")
+  };
+
+  MockRead http_reads[] = {
+    MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+    MockRead(SYNCHRONOUS, 4, kQuicAlternateProtocolHttpHeader),
+    MockRead(SYNCHRONOUS, 5, "hello world"),
+    MockRead(SYNCHRONOUS, OK, 6)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     http_writes, arraysize(http_writes));
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  // In order for a new QUIC session to be established via alternate-protocol
+  // without racing an HTTP connection, we need the host resolution to happen
+  // synchronously.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("www.google.com", "192.168.0.1", "");
+  HostResolver::RequestInfo info(HostPortPair("www.google.com", 80));
+  AddressList address;
+  host_resolver_.Resolve(info,
+                         DEFAULT_PRIORITY,
+                         &address,
+                         CompletionCallback(),
+                         nullptr,
+                         net_log_.bound());
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_P(QuicNetworkTransactionTest, ZeroRTTWithConfirmationRequired) {
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddRead(
+      ConstructResponseHeadersPacket(1, kClientDataStreamId1, false, false,
+                                     GetResponseHeaders("200 OK")));
+  mock_quic_data.AddRead(
+      ConstructDataPacket(2, kClientDataStreamId1, false, true, 0, "hello!"));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddRead(SYNCHRONOUS, 0);  // EOF
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 1);
+
+  // The non-alternate protocol job needs to hang in order to guarantee that
+  // the alternate-protocol job will "win".
+  AddHangingNonAlternateProtocolSocketData();
+
+  // In order for a new QUIC session to be established via alternate-protocol
+  // without racing an HTTP connection, we need the host resolution to happen
+  // synchronously.  Of course, even though QUIC *could* perform a 0-RTT
+  // connection to the the server, in this test we require confirmation
+  // before encrypting so the HTTP job will still start.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("www.google.com", "192.168.0.1", "");
+  HostResolver::RequestInfo info(HostPortPair("www.google.com", 80));
+  AddressList address;
+  host_resolver_.Resolve(info, DEFAULT_PRIORITY, &address,
+                         CompletionCallback(), nullptr, net_log_.bound());
+
+  CreateSessionWithNextProtos();
+  session_->quic_stream_factory()->set_require_confirmation(true);
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+
+  scoped_ptr<HttpNetworkTransaction> trans(
+      new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+  TestCompletionCallback callback;
+  int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
+      QuicSession::HANDSHAKE_CONFIRMED);
+  EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_P(QuicNetworkTransactionTest, BrokenAlternateProtocol) {
+  // Alternate-protocol job
+  scoped_ptr<QuicEncryptedPacket> close(ConstructConnectionClosePacket(1));
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, close->data(), close->length()),
+    MockRead(ASYNC, OK),  // EOF
+  };
+  StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job which will succeed even though the alternate job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+  SendRequestAndExpectHttpResponse("hello from http");
+  ExpectBrokenAlternateProtocolMapping();
+}
+
+TEST_P(QuicNetworkTransactionTest, BrokenAlternateProtocolReadError) {
+  // Alternate-protocol job
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+  };
+  StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job which will succeed even though the alternate job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+  SendRequestAndExpectHttpResponse("hello from http");
+  ExpectBrokenAlternateProtocolMapping();
+}
+
+TEST_P(QuicNetworkTransactionTest, NoBrokenAlternateProtocolIfTcpFails) {
+  // Alternate-protocol job will fail when the session attempts to read.
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+  };
+  StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job will also fail.
+  MockRead http_reads[] = {
+    MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  http_data.set_connect_data(MockConnect(ASYNC, ERR_SOCKET_NOT_CONNECTED));
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+  scoped_ptr<HttpNetworkTransaction> trans(
+      new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+  TestCompletionCallback callback;
+  int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, callback.WaitForResult());
+  ExpectQuicAlternateProtocolMapping();
+}
+
+TEST_P(QuicNetworkTransactionTest, FailedZeroRttBrokenAlternateProtocol) {
+  // Alternate-protocol job
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+  };
+  StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  AddHangingNonAlternateProtocolSocketData();
+
+  // Second Alternate-protocol job which will race with the TCP job.
+  StaticSocketDataProvider quic_data2(quic_reads, arraysize(quic_reads),
+                                      nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data2);
+
+  // Final job that will proceed when the QUIC job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+
+  SendRequestAndExpectHttpResponse("hello from http");
+
+  ExpectBrokenAlternateProtocolMapping();
+
+  EXPECT_TRUE(quic_data.at_read_eof());
+  EXPECT_TRUE(quic_data.at_write_eof());
+}
+
+TEST_P(QuicNetworkTransactionTest, DISABLED_HangingZeroRttFallback) {
+  // Alternate-protocol job
+  MockRead quic_reads[] = {
+    MockRead(ASYNC, ERR_IO_PENDING),
+  };
+  StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job that will proceed when the QUIC job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+
+  SendRequestAndExpectHttpResponse("hello from http");
+}
+
+TEST_P(QuicNetworkTransactionTest, BrokenAlternateProtocolOnConnectFailure) {
+  // Alternate-protocol job will fail before creating a QUIC session.
+  StaticSocketDataProvider quic_data(nullptr, 0, nullptr, 0);
+  quic_data.set_connect_data(MockConnect(SYNCHRONOUS,
+                                         ERR_INTERNET_DISCONNECTED));
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job which will succeed even though the alternate job fails.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+  SendRequestAndExpectHttpResponse("hello from http");
+
+  ExpectBrokenAlternateProtocolMapping();
+}
+
+TEST_P(QuicNetworkTransactionTest, ConnectionCloseDuringConnect) {
+  MockQuicData mock_quic_data;
+  mock_quic_data.AddRead(ConstructConnectionClosePacket(1));
+  mock_quic_data.AddWrite(
+      ConstructRequestHeadersPacket(1, kClientDataStreamId1, true, true,
+                                    GetRequestHeaders("GET", "http", "/")));
+  mock_quic_data.AddWrite(ConstructAckPacket(2, 1));
+  mock_quic_data.AddDelayedSocketDataToFactory(&socket_factory_, 0);
+
+  // When the QUIC connection fails, we will try the request again over HTTP.
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n"),
+    MockRead(kQuicAlternateProtocolHttpHeader),
+    MockRead("hello world"),
+    MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+    MockRead(ASYNC, OK)
+  };
+
+  StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+                                     nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&http_data);
+
+  // In order for a new QUIC session to be established via alternate-protocol
+  // without racing an HTTP connection, we need the host resolution to happen
+  // synchronously.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("www.google.com", "192.168.0.1", "");
+  HostResolver::RequestInfo info(HostPortPair("www.google.com", 80));
+  AddressList address;
+  host_resolver_.Resolve(info,
+                         DEFAULT_PRIORITY,
+                         &address,
+                         CompletionCallback(),
+                         nullptr,
+                         net_log_.bound());
+
+  CreateSessionWithNextProtos();
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+  SendRequestAndExpectHttpResponse("hello world");
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_packet_creator.cc b/net/quic/quic_packet_creator.cc
new file mode 100644
index 0000000..17a4a10
--- /dev/null
+++ b/net/quic/quic_packet_creator.cc
@@ -0,0 +1,522 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_packet_creator.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::max;
+using std::min;
+using std::pair;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// Default max packets in an FEC group.
+static const size_t kDefaultMaxPacketsPerFecGroup = 10;
+// Lowest max packets in an FEC group.
+static const size_t kLowestMaxPacketsPerFecGroup = 2;
+
+}  // namespace
+
+// A QuicRandom wrapper that gets a bucket of entropy and distributes it
+// bit-by-bit. Replenishes the bucket as needed. Not thread-safe. Expose this
+// class if single bit randomness is needed elsewhere.
+class QuicRandomBoolSource {
+ public:
+  // random: Source of entropy. Not owned.
+  explicit QuicRandomBoolSource(QuicRandom* random)
+      : random_(random),
+        bit_bucket_(0),
+        bit_mask_(0) {}
+
+  ~QuicRandomBoolSource() {}
+
+  // Returns the next random bit from the bucket.
+  bool RandBool() {
+    if (bit_mask_ == 0) {
+      bit_bucket_ = random_->RandUint64();
+      bit_mask_ = 1;
+    }
+    bool result = ((bit_bucket_ & bit_mask_) != 0);
+    bit_mask_ <<= 1;
+    return result;
+  }
+
+ private:
+  // Source of entropy.
+  QuicRandom* random_;
+  // Stored random bits.
+  uint64 bit_bucket_;
+  // The next available bit has "1" in the mask. Zero means empty bucket.
+  uint64 bit_mask_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicRandomBoolSource);
+};
+
+QuicPacketCreator::QuicPacketCreator(QuicConnectionId connection_id,
+                                     QuicFramer* framer,
+                                     QuicRandom* random_generator)
+    : connection_id_(connection_id),
+      encryption_level_(ENCRYPTION_NONE),
+      framer_(framer),
+      random_bool_source_(new QuicRandomBoolSource(random_generator)),
+      sequence_number_(0),
+      should_fec_protect_(false),
+      fec_group_number_(0),
+      send_version_in_packet_(!framer->is_server()),
+      max_packet_length_(kDefaultMaxPacketSize),
+      max_packets_per_fec_group_(kDefaultMaxPacketsPerFecGroup),
+      connection_id_length_(PACKET_8BYTE_CONNECTION_ID),
+      next_sequence_number_length_(PACKET_1BYTE_SEQUENCE_NUMBER),
+      sequence_number_length_(next_sequence_number_length_),
+      packet_size_(0) {
+  framer_->set_fec_builder(this);
+}
+
+QuicPacketCreator::~QuicPacketCreator() {
+}
+
+void QuicPacketCreator::OnBuiltFecProtectedPayload(
+    const QuicPacketHeader& header, StringPiece payload) {
+  if (fec_group_.get()) {
+    DCHECK_NE(0u, header.fec_group);
+    fec_group_->Update(encryption_level_, header, payload);
+  }
+}
+
+void QuicPacketCreator::set_max_packets_per_fec_group(
+    size_t max_packets_per_fec_group) {
+  max_packets_per_fec_group_ = max(kLowestMaxPacketsPerFecGroup,
+                                   max_packets_per_fec_group);
+  DCHECK_LT(0u, max_packets_per_fec_group_);
+}
+
+bool QuicPacketCreator::ShouldSendFec(bool force_close) const {
+  DCHECK(!HasPendingFrames());
+  return fec_group_.get() != nullptr && fec_group_->NumReceivedPackets() > 0 &&
+         (force_close ||
+          fec_group_->NumReceivedPackets() >= max_packets_per_fec_group_);
+}
+
+bool QuicPacketCreator::IsFecGroupOpen() const {
+  return fec_group_.get() != nullptr;
+}
+
+void QuicPacketCreator::StartFecProtectingPackets() {
+  if (!IsFecEnabled()) {
+    LOG(DFATAL) << "Cannot start FEC protection when FEC is not enabled.";
+    return;
+  }
+  // TODO(jri): This currently requires that the generator flush out any
+  // pending frames when FEC protection is turned on. If current packet can be
+  // converted to an FEC protected packet, do it. This will require the
+  // generator to check if the resulting expansion still allows the incoming
+  // frame to be added to the packet.
+  if (HasPendingFrames()) {
+    LOG(DFATAL) << "Cannot start FEC protection with pending frames.";
+    return;
+  }
+  DCHECK(!should_fec_protect_);
+  should_fec_protect_ = true;
+}
+
+void QuicPacketCreator::StopFecProtectingPackets() {
+  if (fec_group_.get() != nullptr) {
+    LOG(DFATAL) << "Cannot stop FEC protection with open FEC group.";
+    return;
+  }
+  DCHECK(should_fec_protect_);
+  should_fec_protect_ = false;
+  fec_group_number_ = 0;
+}
+
+bool QuicPacketCreator::IsFecProtected() const {
+  return should_fec_protect_;
+}
+
+bool QuicPacketCreator::IsFecEnabled() const {
+  return max_packets_per_fec_group_ > 0;
+}
+
+InFecGroup QuicPacketCreator::MaybeUpdateLengthsAndStartFec() {
+  if (fec_group_.get() != nullptr) {
+    // Don't update any lengths when an FEC group is open, to ensure same
+    // packet header size in all packets within a group.
+    return IN_FEC_GROUP;
+  }
+  if (!queued_frames_.empty()) {
+    // Don't change creator state if there are frames queued.
+    return fec_group_.get() == nullptr ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+  }
+
+  // Update sequence number length only on packet and FEC group boundaries.
+  sequence_number_length_ = next_sequence_number_length_;
+
+  if (!should_fec_protect_) {
+    return NOT_IN_FEC_GROUP;
+  }
+  // Start a new FEC group since protection is on. Set the fec group number to
+  // the sequence number of the next packet.
+  fec_group_number_ = sequence_number() + 1;
+  fec_group_.reset(new QuicFecGroup());
+  return IN_FEC_GROUP;
+}
+
+// Stops serializing version of the protocol in packets sent after this call.
+// A packet that is already open might send kQuicVersionSize bytes less than the
+// maximum packet size if we stop sending version before it is serialized.
+void QuicPacketCreator::StopSendingVersion() {
+  DCHECK(send_version_in_packet_);
+  send_version_in_packet_ = false;
+  if (packet_size_ > 0) {
+    DCHECK_LT(kQuicVersionSize, packet_size_);
+    packet_size_ -= kQuicVersionSize;
+  }
+}
+
+void QuicPacketCreator::UpdateSequenceNumberLength(
+      QuicPacketSequenceNumber least_packet_awaited_by_peer,
+      QuicByteCount congestion_window) {
+  DCHECK_LE(least_packet_awaited_by_peer, sequence_number_ + 1);
+  // Since the packet creator will not change sequence number length mid FEC
+  // group, include the size of an FEC group to be safe.
+  const QuicPacketSequenceNumber current_delta =
+      max_packets_per_fec_group_ + sequence_number_ + 1
+      - least_packet_awaited_by_peer;
+  const uint64 congestion_window_packets =
+      congestion_window / max_packet_length_;
+  const uint64 delta = max(current_delta, congestion_window_packets);
+  next_sequence_number_length_ =
+      QuicFramer::GetMinSequenceNumberLength(delta * 4);
+}
+
+bool QuicPacketCreator::HasRoomForStreamFrame(QuicStreamId id,
+                                              QuicStreamOffset offset) const {
+  // TODO(jri): This is a simple safe decision for now, but make
+  // is_in_fec_group a parameter. Same as with all public methods in
+  // QuicPacketCreator.
+  return BytesFree() >
+      QuicFramer::GetMinStreamFrameSize(id, offset, true,
+                                        should_fec_protect_ ? IN_FEC_GROUP :
+                                                              NOT_IN_FEC_GROUP);
+}
+
+// static
+size_t QuicPacketCreator::StreamFramePacketOverhead(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length,
+    QuicStreamOffset offset,
+    InFecGroup is_in_fec_group) {
+  return GetPacketHeaderSize(connection_id_length, include_version,
+                             sequence_number_length, is_in_fec_group) +
+      // Assumes this is a stream with a single lone packet.
+      QuicFramer::GetMinStreamFrameSize(1u, offset, true, is_in_fec_group);
+}
+
+size_t QuicPacketCreator::CreateStreamFrame(QuicStreamId id,
+                                            const IOVector& data,
+                                            QuicStreamOffset offset,
+                                            bool fin,
+                                            QuicFrame* frame) {
+  DCHECK_GT(max_packet_length_, StreamFramePacketOverhead(
+                PACKET_8BYTE_CONNECTION_ID, kIncludeVersion,
+                PACKET_6BYTE_SEQUENCE_NUMBER, offset, IN_FEC_GROUP));
+
+  InFecGroup is_in_fec_group = MaybeUpdateLengthsAndStartFec();
+
+  LOG_IF(DFATAL, !HasRoomForStreamFrame(id, offset))
+      << "No room for Stream frame, BytesFree: " << BytesFree()
+      << " MinStreamFrameSize: "
+      << QuicFramer::GetMinStreamFrameSize(id, offset, true, is_in_fec_group);
+
+  if (data.Empty()) {
+    LOG_IF(DFATAL, !fin)
+        << "Creating a stream frame with no data or fin.";
+    // Create a new packet for the fin, if necessary.
+    *frame = QuicFrame(new QuicStreamFrame(id, true, offset, data));
+    return 0;
+  }
+
+  const size_t data_size = data.TotalBufferSize();
+  size_t min_frame_size = QuicFramer::GetMinStreamFrameSize(
+      id, offset, /* last_frame_in_packet= */ true, is_in_fec_group);
+  size_t bytes_consumed = min<size_t>(BytesFree() - min_frame_size, data_size);
+
+  bool set_fin = fin && bytes_consumed == data_size;  // Last frame.
+  IOVector frame_data;
+  frame_data.AppendIovecAtMostBytes(data.iovec(), data.Size(),
+                                    bytes_consumed);
+  DCHECK_EQ(frame_data.TotalBufferSize(), bytes_consumed);
+  *frame = QuicFrame(new QuicStreamFrame(id, set_fin, offset, frame_data));
+  return bytes_consumed;
+}
+
+size_t QuicPacketCreator::CreateStreamFrameWithNotifier(
+    QuicStreamId id,
+    const IOVector& data,
+    QuicStreamOffset offset,
+    bool fin,
+    QuicAckNotifier* notifier,
+    QuicFrame* frame) {
+  size_t bytes_consumed = CreateStreamFrame(id, data, offset, fin, frame);
+
+  // The frame keeps track of the QuicAckNotifier until it is serialized into
+  // a packet. At that point the notifier is informed of the sequence number
+  // of the packet that this frame was eventually sent in.
+  frame->stream_frame->notifier = notifier;
+
+  return bytes_consumed;
+}
+
+SerializedPacket QuicPacketCreator::ReserializeAllFrames(
+    const QuicFrames& frames,
+    QuicSequenceNumberLength original_length) {
+  DCHECK(fec_group_.get() == nullptr);
+  const QuicSequenceNumberLength saved_length = sequence_number_length_;
+  const QuicSequenceNumberLength saved_next_length =
+      next_sequence_number_length_;
+  const bool saved_should_fec_protect = should_fec_protect_;
+
+  // Temporarily set the sequence number length and stop FEC protection.
+  sequence_number_length_ = original_length;
+  next_sequence_number_length_ = original_length;
+  should_fec_protect_ = false;
+
+  // Serialize the packet and restore the FEC and sequence number length state.
+  SerializedPacket serialized_packet = SerializeAllFrames(frames);
+  sequence_number_length_ = saved_length;
+  next_sequence_number_length_ = saved_next_length;
+  should_fec_protect_ = saved_should_fec_protect;
+
+  return serialized_packet;
+}
+
+SerializedPacket QuicPacketCreator::SerializeAllFrames(
+    const QuicFrames& frames) {
+  // TODO(satyamshekhar): Verify that this DCHECK won't fail. What about queued
+  // frames from SendStreamData()[send_stream_should_flush_ == false &&
+  // data.empty() == true] and retransmit due to RTO.
+  DCHECK_EQ(0u, queued_frames_.size());
+  LOG_IF(DFATAL, frames.empty())
+      << "Attempt to serialize empty packet";
+  for (size_t i = 0; i < frames.size(); ++i) {
+    bool success = AddFrame(frames[i], false);
+    DCHECK(success);
+  }
+  SerializedPacket packet = SerializePacket();
+  DCHECK(packet.retransmittable_frames == nullptr);
+  return packet;
+}
+
+bool QuicPacketCreator::HasPendingFrames() const {
+  return !queued_frames_.empty();
+}
+
+bool QuicPacketCreator::HasPendingRetransmittableFrames() const {
+  return queued_retransmittable_frames_.get() != nullptr &&
+         !queued_retransmittable_frames_->frames().empty();
+}
+
+size_t QuicPacketCreator::ExpansionOnNewFrame() const {
+  // If packet is FEC protected, there's no expansion.
+  if (should_fec_protect_) {
+      return 0;
+  }
+  // If the last frame in the packet is a stream frame, then it will expand to
+  // include the stream_length field when a new frame is added.
+  bool has_trailing_stream_frame =
+      !queued_frames_.empty() && queued_frames_.back().type == STREAM_FRAME;
+  return has_trailing_stream_frame ? kQuicStreamPayloadLengthSize : 0;
+}
+
+size_t QuicPacketCreator::BytesFree() const {
+  const size_t max_plaintext_size =
+      framer_->GetMaxPlaintextSize(max_packet_length_);
+  DCHECK_GE(max_plaintext_size, PacketSize());
+  return max_plaintext_size - min(max_plaintext_size, PacketSize()
+                                  + ExpansionOnNewFrame());
+}
+
+size_t QuicPacketCreator::PacketSize() const {
+  if (!queued_frames_.empty()) {
+    return packet_size_;
+  }
+  if (fec_group_.get() == nullptr) {
+    // Update sequence number length on packet and FEC boundary.
+    sequence_number_length_ = next_sequence_number_length_;
+  }
+  packet_size_ = GetPacketHeaderSize(
+      connection_id_length_, send_version_in_packet_, sequence_number_length_,
+      should_fec_protect_ ? IN_FEC_GROUP : NOT_IN_FEC_GROUP);
+  return packet_size_;
+}
+
+bool QuicPacketCreator::AddSavedFrame(const QuicFrame& frame) {
+  return AddFrame(frame, true);
+}
+
+SerializedPacket QuicPacketCreator::SerializePacket() {
+  LOG_IF(DFATAL, queued_frames_.empty())
+      << "Attempt to serialize empty packet";
+  DCHECK_GE(sequence_number_ + 1, fec_group_number_);
+  QuicPacketHeader header;
+  FillPacketHeader(should_fec_protect_ ? fec_group_number_ : 0, false, &header);
+
+  MaybeAddPadding();
+
+  size_t max_plaintext_size =
+      framer_->GetMaxPlaintextSize(max_packet_length_);
+  DCHECK_GE(max_plaintext_size, packet_size_);
+  // ACK Frames will be truncated due to length only if they're the only frame
+  // in the packet, and if packet_size_ was set to max_plaintext_size. If
+  // truncation due to length occurred, then GetSerializedFrameLength will have
+  // returned all bytes free.
+  bool possibly_truncated_by_length = packet_size_ == max_plaintext_size &&
+      queued_frames_.size() == 1 &&
+      queued_frames_.back().type == ACK_FRAME;
+  SerializedPacket serialized =
+      framer_->BuildDataPacket(header, queued_frames_, packet_size_);
+  LOG_IF(DFATAL, !serialized.packet)
+      << "Failed to serialize " << queued_frames_.size() << " frames.";
+  // Because of possible truncation, we can't be confident that our
+  // packet size calculation worked correctly.
+  if (!possibly_truncated_by_length) {
+    DCHECK_EQ(packet_size_, serialized.packet->length());
+  }
+  packet_size_ = 0;
+  queued_frames_.clear();
+  serialized.retransmittable_frames = queued_retransmittable_frames_.release();
+  return serialized;
+}
+
+SerializedPacket QuicPacketCreator::SerializeFec() {
+  if (fec_group_.get() == nullptr || fec_group_->NumReceivedPackets() <= 0) {
+    LOG(DFATAL) << "SerializeFEC called but no group or zero packets in group.";
+    // TODO(jri): Make this a public method of framer?
+    SerializedPacket kNoPacket(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0,
+                               nullptr);
+    return kNoPacket;
+  }
+  DCHECK_EQ(0u, queued_frames_.size());
+  QuicPacketHeader header;
+  FillPacketHeader(fec_group_number_, true, &header);
+  QuicFecData fec_data;
+  fec_data.fec_group = fec_group_->min_protected_packet();
+  fec_data.redundancy = fec_group_->payload_parity();
+  SerializedPacket serialized = framer_->BuildFecPacket(header, fec_data);
+  fec_group_.reset(nullptr);
+  packet_size_ = 0;
+  LOG_IF(DFATAL, !serialized.packet)
+      << "Failed to serialize fec packet for group:" << fec_data.fec_group;
+  DCHECK_GE(max_packet_length_, serialized.packet->length());
+  return serialized;
+}
+
+SerializedPacket QuicPacketCreator::SerializeConnectionClose(
+    QuicConnectionCloseFrame* close_frame) {
+  QuicFrames frames;
+  frames.push_back(QuicFrame(close_frame));
+  return SerializeAllFrames(frames);
+}
+
+QuicEncryptedPacket* QuicPacketCreator::SerializeVersionNegotiationPacket(
+    const QuicVersionVector& supported_versions) {
+  DCHECK(framer_->is_server());
+  QuicPacketPublicHeader header;
+  header.connection_id = connection_id_;
+  header.reset_flag = false;
+  header.version_flag = true;
+  header.versions = supported_versions;
+  QuicEncryptedPacket* encrypted =
+      framer_->BuildVersionNegotiationPacket(header, supported_versions);
+  DCHECK(encrypted);
+  DCHECK_GE(max_packet_length_, encrypted->length());
+  return encrypted;
+}
+
+void QuicPacketCreator::FillPacketHeader(QuicFecGroupNumber fec_group,
+                                         bool fec_flag,
+                                         QuicPacketHeader* header) {
+  header->public_header.connection_id = connection_id_;
+  header->public_header.reset_flag = false;
+  header->public_header.version_flag = send_version_in_packet_;
+  header->fec_flag = fec_flag;
+  header->packet_sequence_number = ++sequence_number_;
+  header->public_header.sequence_number_length = sequence_number_length_;
+  header->entropy_flag = random_bool_source_->RandBool();
+  header->is_in_fec_group = fec_group == 0 ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+  header->fec_group = fec_group;
+}
+
+bool QuicPacketCreator::ShouldRetransmit(const QuicFrame& frame) {
+  switch (frame.type) {
+    case ACK_FRAME:
+    case CONGESTION_FEEDBACK_FRAME:
+    case PADDING_FRAME:
+    case STOP_WAITING_FRAME:
+      return false;
+    default:
+      return true;
+  }
+}
+
+bool QuicPacketCreator::AddFrame(const QuicFrame& frame,
+                                 bool save_retransmittable_frames) {
+  DVLOG(1) << "Adding frame: " << frame;
+  InFecGroup is_in_fec_group = MaybeUpdateLengthsAndStartFec();
+
+  size_t frame_len = framer_->GetSerializedFrameLength(
+      frame, BytesFree(), queued_frames_.empty(), true, is_in_fec_group,
+      sequence_number_length_);
+  if (frame_len == 0) {
+    return false;
+  }
+  DCHECK_LT(0u, packet_size_);
+  packet_size_ += ExpansionOnNewFrame() + frame_len;
+
+  if (save_retransmittable_frames && ShouldRetransmit(frame)) {
+    if (queued_retransmittable_frames_.get() == nullptr) {
+      queued_retransmittable_frames_.reset(new RetransmittableFrames());
+    }
+    if (frame.type == STREAM_FRAME) {
+      queued_frames_.push_back(
+          queued_retransmittable_frames_->AddStreamFrame(frame.stream_frame));
+    } else {
+      queued_frames_.push_back(
+          queued_retransmittable_frames_->AddNonStreamFrame(frame));
+    }
+  } else {
+    queued_frames_.push_back(frame);
+  }
+  return true;
+}
+
+void QuicPacketCreator::MaybeAddPadding() {
+  if (queued_retransmittable_frames_.get() == nullptr) {
+    return;
+  }
+  if (!queued_retransmittable_frames_->HasCryptoHandshake()) {
+    return;
+  }
+  if (BytesFree() == 0) {
+    // Don't pad full packets.
+    return;
+  }
+  QuicPaddingFrame padding;
+  bool success = AddFrame(QuicFrame(&padding), false);
+  DCHECK(success);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_packet_creator.h b/net/quic/quic_packet_creator.h
new file mode 100644
index 0000000..313632e
--- /dev/null
+++ b/net/quic/quic_packet_creator.h
@@ -0,0 +1,296 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Accumulates frames for the next packet until more frames no longer fit or
+// it's time to create a packet from them.  Also provides packet creation of
+// FEC packets based on previously created packets.
+
+#ifndef NET_QUIC_QUIC_PACKET_CREATOR_H_
+#define NET_QUIC_QUIC_PACKET_CREATOR_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+class QuicPacketCreatorPeer;
+}
+
+class QuicAckNotifier;
+class QuicRandom;
+class QuicRandomBoolSource;
+
+class NET_EXPORT_PRIVATE QuicPacketCreator : public QuicFecBuilderInterface {
+ public:
+  // QuicRandom* required for packet entropy.
+  QuicPacketCreator(QuicConnectionId connection_id,
+                    QuicFramer* framer,
+                    QuicRandom* random_generator);
+
+  virtual ~QuicPacketCreator();
+
+  // QuicFecBuilderInterface
+  virtual void OnBuiltFecProtectedPayload(const QuicPacketHeader& header,
+                                          base::StringPiece payload) OVERRIDE;
+
+  // Turn on FEC protection for subsequently created packets. FEC should be
+  // enabled first (max_packets_per_fec_group should be non-zero) for FEC
+  // protection to start.
+  void StartFecProtectingPackets();
+
+  // Turn off FEC protection for subsequently created packets. If the creator
+  // has any open FEC group, call will fail. It is the caller's responsibility
+  // to flush out FEC packets in generation, and to verify with ShouldSendFec()
+  // that there is no open FEC group.
+  void StopFecProtectingPackets();
+
+  // Checks if it's time to send an FEC packet.  |force_close| forces this to
+  // return true if an FEC group is open.
+  bool ShouldSendFec(bool force_close) const;
+
+  // Returns true if an FEC packet is under construction.
+  bool IsFecGroupOpen() const;
+
+  // Makes the framer not serialize the protocol version in sent packets.
+  void StopSendingVersion();
+
+  // Update the sequence number length to use in future packets as soon as it
+  // can be safely changed.
+  void UpdateSequenceNumberLength(
+      QuicPacketSequenceNumber least_packet_awaited_by_peer,
+      QuicByteCount congestion_window);
+
+  // The overhead the framing will add for a packet with one frame.
+  static size_t StreamFramePacketOverhead(
+      QuicConnectionIdLength connection_id_length,
+      bool include_version,
+      QuicSequenceNumberLength sequence_number_length,
+      QuicStreamOffset offset,
+      InFecGroup is_in_fec_group);
+
+  bool HasRoomForStreamFrame(QuicStreamId id, QuicStreamOffset offset) const;
+
+  // Converts a raw payload to a frame which fits into the currently open
+  // packet if there is one.  Returns the number of bytes consumed from data.
+  // If data is empty and fin is true, the expected behavior is to consume the
+  // fin but return 0.
+  size_t CreateStreamFrame(QuicStreamId id,
+                           const IOVector& data,
+                           QuicStreamOffset offset,
+                           bool fin,
+                           QuicFrame* frame);
+
+  // As above, but keeps track of an QuicAckNotifier that should be called when
+  // the packet that contains this stream frame is ACKed.
+  // The |notifier| is not owned by the QuicPacketGenerator and must outlive the
+  // generated packet.
+  size_t CreateStreamFrameWithNotifier(QuicStreamId id,
+                                       const IOVector& data,
+                                       QuicStreamOffset offset,
+                                       bool fin,
+                                       QuicAckNotifier* notifier,
+                                       QuicFrame* frame);
+
+  // Serializes all frames into a single packet. All frames must fit into a
+  // single packet. Also, sets the entropy hash of the serialized packet to a
+  // random bool and returns that value as a member of SerializedPacket.
+  // Never returns a RetransmittableFrames in SerializedPacket.
+  SerializedPacket SerializeAllFrames(const QuicFrames& frames);
+
+  // Re-serializes frames with the original packet's sequence number length.
+  // Used for retransmitting packets to ensure they aren't too long.
+  // Caller must ensure that any open FEC group is closed before calling this
+  // method.
+  SerializedPacket ReserializeAllFrames(
+      const QuicFrames& frames,
+      QuicSequenceNumberLength original_length);
+
+  // Returns true if there are frames pending to be serialized.
+  bool HasPendingFrames() const;
+
+  // Returns true if there are retransmittable frames pending to be serialized.
+  bool HasPendingRetransmittableFrames() const;
+
+  // Returns whether FEC protection is currently enabled. Note: Enabled does not
+  // mean that an FEC group is currently active; i.e., IsFecProtected() may
+  // still return false.
+  bool IsFecEnabled() const;
+
+  // Returns true if subsequent packets will be FEC protected. Note: True does
+  // not mean that an FEC packet is currently under construction; i.e.,
+  // fec_group_.get() may still be nullptr, until MaybeStartFec() is called.
+  bool IsFecProtected() const;
+
+  // Returns the number of bytes which are available to be used by additional
+  // frames in the packet.  Since stream frames are slightly smaller when they
+  // are the last frame in a packet, this method will return a different
+  // value than max_packet_size - PacketSize(), in this case.
+  size_t BytesFree() const;
+
+  // Returns the number of bytes that the packet will expand by if a new frame
+  // is added to the packet. If the last frame was a stream frame, it will
+  // expand slightly when a new frame is added, and this method returns the
+  // amount of expected expansion. If the packet is in an FEC group, no
+  // expansion happens and this method always returns zero.
+  size_t ExpansionOnNewFrame() const;
+
+  // Returns the number of bytes in the current packet, including the header,
+  // if serialized with the current frames.  Adding a frame to the packet
+  // may change the serialized length of existing frames, as per the comment
+  // in BytesFree.
+  size_t PacketSize() const;
+
+  // TODO(jri): AddSavedFrame calls AddFrame, which only saves the frame
+  // if it is a stream frame, not other types of frames. Fix this API;
+  // add a AddNonSavedFrame method.
+  // Adds |frame| to the packet creator's list of frames to be serialized.
+  // Returns false if the frame doesn't fit into the current packet.
+  bool AddSavedFrame(const QuicFrame& frame);
+
+  // Serializes all frames which have been added and adds any which should be
+  // retransmitted to |retransmittable_frames| if it's not nullptr. All frames
+  // must fit into a single packet. Sets the entropy hash of the serialized
+  // packet to a random bool and returns that value as a member of
+  // SerializedPacket. Also, sets |serialized_frames| in the SerializedPacket to
+  // the corresponding RetransmittableFrames if any frames are to be
+  // retransmitted.
+  SerializedPacket SerializePacket();
+
+  // Packetize FEC data. All frames must fit into a single packet. Also, sets
+  // the entropy hash of the serialized packet to a random bool and returns
+  // that value as a member of SerializedPacket.
+  SerializedPacket SerializeFec();
+
+  // Creates a packet with connection close frame. Caller owns the created
+  // packet. Also, sets the entropy hash of the serialized packet to a random
+  // bool and returns that value as a member of SerializedPacket.
+  SerializedPacket SerializeConnectionClose(
+      QuicConnectionCloseFrame* close_frame);
+
+  // Creates a version negotiation packet which supports |supported_versions|.
+  // Caller owns the created  packet. Also, sets the entropy hash of the
+  // serialized packet to a random bool and returns that value as a member of
+  // SerializedPacket.
+  QuicEncryptedPacket* SerializeVersionNegotiationPacket(
+      const QuicVersionVector& supported_versions);
+
+  // Sets the encryption level that will be applied to new packets.
+  void set_encryption_level(EncryptionLevel level) {
+    encryption_level_ = level;
+  }
+
+  // Sequence number of the last created packet, or 0 if no packets have been
+  // created.
+  QuicPacketSequenceNumber sequence_number() const {
+    return sequence_number_;
+  }
+
+  void set_sequence_number(QuicPacketSequenceNumber s) {
+    sequence_number_ = s;
+  }
+
+  QuicConnectionIdLength connection_id_length() const {
+    return connection_id_length_;
+  }
+
+  QuicSequenceNumberLength next_sequence_number_length() const {
+    return next_sequence_number_length_;
+  }
+
+  void set_next_sequence_number_length(QuicSequenceNumberLength length) {
+    next_sequence_number_length_ = length;
+  }
+
+  size_t max_packet_length() const {
+    return max_packet_length_;
+  }
+
+  void set_max_packet_length(size_t length) {
+    // |max_packet_length_| should not be changed mid-packet or mid-FEC group.
+    DCHECK(fec_group_.get() == nullptr && queued_frames_.empty());
+    max_packet_length_ = length;
+  }
+
+  // Returns current max number of packets covered by an FEC group.
+  size_t max_packets_per_fec_group() const {
+      return max_packets_per_fec_group_;
+  }
+
+  // Sets creator's max number of packets covered by an FEC group.
+  // Note: While there are no constraints on |max_packets_per_fec_group|,
+  // this setter enforces a min value of kLowestMaxPacketsPerFecGroup.
+  // To turn off FEC protection, use StopFecProtectingPackets().
+  void set_max_packets_per_fec_group(size_t max_packets_per_fec_group);
+
+ private:
+  friend class test::QuicPacketCreatorPeer;
+
+  static bool ShouldRetransmit(const QuicFrame& frame);
+
+  // Updates sequence number and max packet lengths on a packet or FEC group
+  // boundary.
+  void MaybeUpdateLengths();
+
+  // Updates lengths and also starts an FEC group if FEC protection is on and
+  // there is not already an FEC group open.
+  InFecGroup MaybeUpdateLengthsAndStartFec();
+
+  void FillPacketHeader(QuicFecGroupNumber fec_group,
+                        bool fec_flag,
+                        QuicPacketHeader* header);
+
+  // Allows a frame to be added without creating retransmittable frames.
+  // Particularly useful for retransmits using SerializeAllFrames().
+  bool AddFrame(const QuicFrame& frame, bool save_retransmittable_frames);
+
+  // Adds a padding frame to the current packet only if the current packet
+  // contains a handshake message, and there is sufficient room to fit a
+  // padding frame.
+  void MaybeAddPadding();
+
+  QuicConnectionId connection_id_;
+  EncryptionLevel encryption_level_;
+  QuicFramer* framer_;
+  scoped_ptr<QuicRandomBoolSource> random_bool_source_;
+  QuicPacketSequenceNumber sequence_number_;
+  // If true, any created packets will be FEC protected.
+  bool should_fec_protect_;
+  QuicFecGroupNumber fec_group_number_;
+  scoped_ptr<QuicFecGroup> fec_group_;
+  // Controls whether protocol version should be included while serializing the
+  // packet.
+  bool send_version_in_packet_;
+  // Maximum length including headers and encryption (UDP payload length.)
+  size_t max_packet_length_;
+  // 0 indicates FEC is disabled.
+  size_t max_packets_per_fec_group_;
+  // Length of connection_id to send over the wire.
+  QuicConnectionIdLength connection_id_length_;
+  // Staging variable to hold next packet sequence number length. When sequence
+  // number length is to be changed, this variable holds the new length until
+  // a packet or FEC group boundary, when the creator's sequence_number_length_
+  // can be changed to this new value.
+  QuicSequenceNumberLength next_sequence_number_length_;
+  // Sequence number length for the current packet and for the current FEC group
+  // when FEC is enabled. Mutable so PacketSize() can adjust it when the packet
+  // is empty.
+  mutable QuicSequenceNumberLength sequence_number_length_;
+  // packet_size_ is mutable because it's just a cache of the current size.
+  // packet_size should never be read directly, use PacketSize() instead.
+  mutable size_t packet_size_;
+  QuicFrames queued_frames_;
+  scoped_ptr<RetransmittableFrames> queued_retransmittable_frames_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicPacketCreator);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_PACKET_CREATOR_H_
diff --git a/net/quic/quic_packet_creator_test.cc b/net/quic/quic_packet_creator_test.cc
new file mode 100644
index 0000000..c8d5686
--- /dev/null
+++ b/net/quic/quic_packet_creator_test.cc
@@ -0,0 +1,1021 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_packet_creator.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::StringPiece;
+using std::ostream;
+using std::string;
+using std::vector;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+// Run tests with combinations of {QuicVersion, ToggleVersionSerialization}.
+struct TestParams {
+  TestParams(QuicVersion version,
+             bool version_serialization)
+      : version(version),
+        version_serialization(version_serialization) {
+  }
+
+  friend ostream& operator<<(ostream& os, const TestParams& p) {
+    os << "{ client_version: " << QuicVersionToString(p.version)
+       << " include version: " << p.version_serialization << " }";
+    return os;
+  }
+
+  QuicVersion version;
+  bool version_serialization;
+};
+
+// Constructs various test permutations.
+vector<TestParams> GetTestParams() {
+  vector<TestParams> params;
+  QuicVersionVector all_supported_versions = QuicSupportedVersions();
+  for (size_t i = 0; i < all_supported_versions.size(); ++i) {
+    params.push_back(TestParams(all_supported_versions[i], true));
+    params.push_back(TestParams(all_supported_versions[i], false));
+  }
+  return params;
+}
+
+class QuicPacketCreatorTest : public ::testing::TestWithParam<TestParams> {
+ protected:
+  QuicPacketCreatorTest()
+      : server_framer_(SupportedVersions(GetParam().version), QuicTime::Zero(),
+                       true),
+        client_framer_(SupportedVersions(GetParam().version), QuicTime::Zero(),
+                       false),
+        sequence_number_(0),
+        connection_id_(2),
+        data_("foo"),
+        creator_(connection_id_, &client_framer_, &mock_random_) {
+    client_framer_.set_visitor(&framer_visitor_);
+    client_framer_.set_received_entropy_calculator(&entropy_calculator_);
+    server_framer_.set_visitor(&framer_visitor_);
+  }
+
+  virtual ~QuicPacketCreatorTest() OVERRIDE {
+  }
+
+  void ProcessPacket(QuicPacket* packet) {
+    scoped_ptr<QuicEncryptedPacket> encrypted(
+        server_framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number_,
+                                     *packet));
+    server_framer_.ProcessPacket(*encrypted);
+  }
+
+  void CheckStreamFrame(const QuicFrame& frame,
+                        QuicStreamId stream_id,
+                        const string& data,
+                        QuicStreamOffset offset,
+                        bool fin) {
+    EXPECT_EQ(STREAM_FRAME, frame.type);
+    ASSERT_TRUE(frame.stream_frame);
+    EXPECT_EQ(stream_id, frame.stream_frame->stream_id);
+    scoped_ptr<string> frame_data(frame.stream_frame->GetDataAsString());
+    EXPECT_EQ(data, *frame_data);
+    EXPECT_EQ(offset, frame.stream_frame->offset);
+    EXPECT_EQ(fin, frame.stream_frame->fin);
+  }
+
+  // Returns the number of bytes consumed by the header of packet, including
+  // the version.
+  size_t GetPacketHeaderOverhead(InFecGroup is_in_fec_group) {
+    return GetPacketHeaderSize(creator_.connection_id_length(),
+                               kIncludeVersion,
+                               creator_.next_sequence_number_length(),
+                               is_in_fec_group);
+  }
+
+  // Returns the number of bytes of overhead that will be added to a packet
+  // of maximum length.
+  size_t GetEncryptionOverhead() {
+    return creator_.max_packet_length() - client_framer_.GetMaxPlaintextSize(
+        creator_.max_packet_length());
+  }
+
+  // Returns the number of bytes consumed by the non-data fields of a stream
+  // frame, assuming it is the last frame in the packet
+  size_t GetStreamFrameOverhead(InFecGroup is_in_fec_group) {
+    return QuicFramer::GetMinStreamFrameSize(kClientDataStreamId1, kOffset,
+                                             true, is_in_fec_group);
+  }
+
+  // Enables and turns on FEC protection. Returns true if FEC protection is on.
+  bool SwitchFecProtectionOn(size_t max_packets_per_fec_group) {
+    creator_.set_max_packets_per_fec_group(max_packets_per_fec_group);
+    creator_.StartFecProtectingPackets();
+    return creator_.IsFecProtected();
+  }
+
+  static const QuicStreamOffset kOffset = 1u;
+
+  QuicFrames frames_;
+  QuicFramer server_framer_;
+  QuicFramer client_framer_;
+  testing::StrictMock<MockFramerVisitor> framer_visitor_;
+  QuicPacketSequenceNumber sequence_number_;
+  QuicConnectionId connection_id_;
+  string data_;
+  MockRandom mock_random_;
+  QuicPacketCreator creator_;
+  MockEntropyCalculator entropy_calculator_;
+};
+
+// Run all packet creator tests with all supported versions of QUIC, and with
+// and without version in the packet header.
+INSTANTIATE_TEST_CASE_P(QuicPacketCreatorTests,
+                        QuicPacketCreatorTest,
+                        ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(QuicPacketCreatorTest, SerializeFrames) {
+  frames_.push_back(QuicFrame(new QuicAckFrame(MakeAckFrame(0u))));
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, true, 0u, IOVector())));
+  SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+  delete frames_[0].ack_frame;
+  delete frames_[1].stream_frame;
+  delete frames_[2].stream_frame;
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+    EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeWithFEC) {
+  // Enable FEC protection, and send FEC packet every 6 packets.
+  EXPECT_TRUE(SwitchFecProtectionOn(6));
+  // Should return false since we do not have enough packets in the FEC group to
+  // trigger an FEC packet.
+  ASSERT_FALSE(creator_.ShouldSendFec(/*force_close=*/false));
+
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+  SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+  delete frames_[0].stream_frame;
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnFecProtectedPayload(_));
+    EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+
+  // Should return false since we do not have enough packets in the FEC group to
+  // trigger an FEC packet.
+  ASSERT_FALSE(creator_.ShouldSendFec(/*force_close=*/false));
+  // Should return true since there are packets in the FEC group.
+  ASSERT_TRUE(creator_.ShouldSendFec(/*force_close=*/true));
+
+  serialized = creator_.SerializeFec();
+  ASSERT_EQ(2u, serialized.sequence_number);
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnFecData(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeChangingSequenceNumberLength) {
+  frames_.push_back(QuicFrame(new QuicAckFrame(MakeAckFrame(0u))));
+  creator_.AddSavedFrame(frames_[0]);
+  creator_.set_next_sequence_number_length(PACKET_4BYTE_SEQUENCE_NUMBER);
+  SerializedPacket serialized = creator_.SerializePacket();
+  // The sequence number length will not change mid-packet.
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+
+  creator_.AddSavedFrame(frames_[0]);
+  serialized = creator_.SerializePacket();
+  // Now the actual sequence number length should have changed.
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+  delete frames_[0].ack_frame;
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, ChangeSequenceNumberLengthMidPacket) {
+  // Changing the sequence number length with queued frames in the creator
+  // should hold the change until after any currently queued frames are
+  // serialized.
+
+  // Packet 1.
+  // Queue a frame in the creator.
+  EXPECT_FALSE(creator_.HasPendingFrames());
+  QuicFrame ack_frame = QuicFrame(new QuicAckFrame(MakeAckFrame(0u)));
+  creator_.AddSavedFrame(ack_frame);
+
+  // Now change sequence number length.
+  creator_.set_next_sequence_number_length(PACKET_4BYTE_SEQUENCE_NUMBER);
+
+  // Add a STOP_WAITING frame since it contains a packet sequence number,
+  // whose length should be 1.
+  QuicStopWaitingFrame stop_waiting_frame;
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&stop_waiting_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  // Ensure the packet is successfully created.
+  SerializedPacket serialized = creator_.SerializePacket();
+  ASSERT_TRUE(serialized.packet);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+
+  // Verify that header in transmitted packet has 1 byte sequence length.
+  QuicPacketHeader header;
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_)).WillOnce(
+        DoAll(SaveArg<0>(&header), Return(true)));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnStopWaitingFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            header.public_header.sequence_number_length);
+  delete serialized.packet;
+
+  // Packet 2.
+  EXPECT_FALSE(creator_.HasPendingFrames());
+  // Generate Packet 2 with one frame -- sequence number length should now
+  // change to 4 bytes.
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&stop_waiting_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  // Ensure the packet is successfully created.
+  serialized = creator_.SerializePacket();
+  ASSERT_TRUE(serialized.packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+
+  // Verify that header in transmitted packet has 4 byte sequence length.
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_)).WillOnce(
+        DoAll(SaveArg<0>(&header), Return(true)));
+    EXPECT_CALL(framer_visitor_, OnStopWaitingFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            header.public_header.sequence_number_length);
+
+  delete serialized.packet;
+  delete ack_frame.ack_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeWithFECChangingSequenceNumberLength) {
+  // Test goal is to test the following sequence (P1 => generate Packet 1):
+  // P1 <change seq num length> P2 FEC,
+  // and we expect that sequence number length should not change until the end
+  // of the open FEC group.
+
+  // Enable FEC protection, and send FEC packet every 6 packets.
+  EXPECT_TRUE(SwitchFecProtectionOn(6));
+  // Should return false since we do not have enough packets in the FEC group to
+  // trigger an FEC packet.
+  ASSERT_FALSE(creator_.ShouldSendFec(/*force_close=*/false));
+  frames_.push_back(QuicFrame(new QuicAckFrame(MakeAckFrame(0u))));
+
+  // Generate Packet 1.
+  creator_.AddSavedFrame(frames_[0]);
+  // Change the sequence number length mid-FEC group and it should not change.
+  creator_.set_next_sequence_number_length(PACKET_4BYTE_SEQUENCE_NUMBER);
+  SerializedPacket serialized = creator_.SerializePacket();
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnFecProtectedPayload(_));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+
+  // Generate Packet 2.
+  creator_.AddSavedFrame(frames_[0]);
+  serialized = creator_.SerializePacket();
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnFecProtectedPayload(_));
+    EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+
+  // Should return false since we do not have enough packets in the FEC group to
+  // trigger an FEC packet.
+  ASSERT_FALSE(creator_.ShouldSendFec(/*force_close=*/false));
+  // Should return true since there are packets in the FEC group.
+  ASSERT_TRUE(creator_.ShouldSendFec(/*force_close=*/true));
+
+  // Force generation of FEC packet.
+  serialized = creator_.SerializeFec();
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+  ASSERT_EQ(3u, serialized.sequence_number);
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnFecData(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+
+  // Ensure the next FEC group starts using the new sequence number length.
+  serialized = creator_.SerializeAllFrames(frames_);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+  delete frames_[0].ack_frame;
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, ReserializeFramesWithSequenceNumberLength) {
+  // If the original packet sequence number length, the current sequence number
+  // length, and the configured send sequence number length are different, the
+  // retransmit must sent with the original length and the others do not change.
+  creator_.set_next_sequence_number_length(PACKET_4BYTE_SEQUENCE_NUMBER);
+  QuicPacketCreatorPeer::SetSequenceNumberLength(&creator_,
+                                                 PACKET_2BYTE_SEQUENCE_NUMBER);
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+  SerializedPacket serialized =
+      creator_.ReserializeAllFrames(frames_, PACKET_1BYTE_SEQUENCE_NUMBER);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            QuicPacketCreatorPeer::GetSequenceNumberLength(&creator_));
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER, serialized.sequence_number_length);
+  delete frames_[0].stream_frame;
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+    EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeConnectionClose) {
+  QuicConnectionCloseFrame frame;
+  frame.error_code = QUIC_NO_ERROR;
+  frame.error_details = "error";
+
+  SerializedPacket serialized = creator_.SerializeConnectionClose(&frame);
+  ASSERT_EQ(1u, serialized.sequence_number);
+  ASSERT_EQ(1u, creator_.sequence_number());
+
+  InSequence s;
+  EXPECT_CALL(framer_visitor_, OnPacket());
+  EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+  EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+  EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+  EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+  EXPECT_CALL(framer_visitor_, OnConnectionCloseFrame(_));
+  EXPECT_CALL(framer_visitor_, OnPacketComplete());
+
+  ProcessPacket(serialized.packet);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, SwitchFecOnOffWithNoGroup) {
+  // Enable FEC protection.
+  creator_.set_max_packets_per_fec_group(6);
+  EXPECT_TRUE(creator_.IsFecEnabled());
+  EXPECT_FALSE(creator_.IsFecProtected());
+
+  // Turn on FEC protection.
+  creator_.StartFecProtectingPackets();
+  EXPECT_TRUE(creator_.IsFecProtected());
+  // We have no packets in the FEC group, so no FEC packet can be created.
+  EXPECT_FALSE(creator_.ShouldSendFec(/*force_close=*/true));
+  // Since no packets are in FEC group yet, we should be able to turn FEC
+  // off with no trouble.
+  creator_.StopFecProtectingPackets();
+  EXPECT_FALSE(creator_.IsFecProtected());
+}
+
+TEST_P(QuicPacketCreatorTest, SwitchFecOnOffWithGroupInProgress) {
+  // Enable FEC protection, and send FEC packet every 6 packets.
+  EXPECT_TRUE(SwitchFecProtectionOn(6));
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+  SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+  delete frames_[0].stream_frame;
+  delete serialized.packet;
+
+  EXPECT_TRUE(creator_.IsFecProtected());
+  // We do not have enough packets in the FEC group to trigger an FEC packet.
+  EXPECT_FALSE(creator_.ShouldSendFec(/*force_close=*/false));
+  // Should return true since there are packets in the FEC group.
+  EXPECT_TRUE(creator_.ShouldSendFec(/*force_close=*/true));
+
+  // Switching FEC off should not change creator state, since there is an
+  // FEC packet under construction.
+  EXPECT_DFATAL(creator_.StopFecProtectingPackets(),
+                "Cannot stop FEC protection with open FEC group.");
+  EXPECT_TRUE(creator_.IsFecProtected());
+  // Confirm that FEC packet is still under construction.
+  EXPECT_TRUE(creator_.ShouldSendFec(/*force_close=*/true));
+
+  serialized = creator_.SerializeFec();
+  delete serialized.packet;
+
+  // Switching FEC on/off should work now.
+  creator_.StopFecProtectingPackets();
+  EXPECT_FALSE(creator_.IsFecProtected());
+  creator_.StartFecProtectingPackets();
+  EXPECT_TRUE(creator_.IsFecProtected());
+}
+
+TEST_P(QuicPacketCreatorTest, SwitchFecOnWithStreamFrameQueued) {
+  // Add a stream frame to the creator.
+  QuicFrame frame;
+  size_t consumed = creator_.CreateStreamFrame(
+      1u, MakeIOVector("test"), 0u, false, &frame);
+  EXPECT_EQ(4u, consumed);
+  ASSERT_TRUE(frame.stream_frame);
+  EXPECT_TRUE(creator_.AddSavedFrame(frame));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  // Enable FEC protection, and send FEC packet every 6 packets.
+  creator_.set_max_packets_per_fec_group(6);
+  EXPECT_TRUE(creator_.IsFecEnabled());
+  EXPECT_DFATAL(creator_.StartFecProtectingPackets(),
+                "Cannot start FEC protection with pending frames.");
+  EXPECT_FALSE(creator_.IsFecProtected());
+
+  // Serialize packet for transmission.
+  SerializedPacket serialized = creator_.SerializePacket();
+  delete serialized.packet;
+  delete serialized.retransmittable_frames;
+  EXPECT_FALSE(creator_.HasPendingFrames());
+
+  // Since all pending frames have been serialized, turning FEC on should work.
+  creator_.StartFecProtectingPackets();
+  EXPECT_TRUE(creator_.IsFecProtected());
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrame) {
+  QuicFrame frame;
+  size_t consumed = creator_.CreateStreamFrame(1u, MakeIOVector("test"), 0u,
+                                               false, &frame);
+  EXPECT_EQ(4u, consumed);
+  CheckStreamFrame(frame, 1u, "test", 0u, false);
+  delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrameFin) {
+  QuicFrame frame;
+  size_t consumed = creator_.CreateStreamFrame(1u, MakeIOVector("test"), 10u,
+                                               true, &frame);
+  EXPECT_EQ(4u, consumed);
+  CheckStreamFrame(frame, 1u, "test", 10u, true);
+  delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrameFinOnly) {
+  QuicFrame frame;
+  size_t consumed = creator_.CreateStreamFrame(1u, IOVector(), 0u, true,
+                                               &frame);
+  EXPECT_EQ(0u, consumed);
+  CheckStreamFrame(frame, 1u, string(), 0u, true);
+  delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, CreateAllFreeBytesForStreamFrames) {
+  const size_t overhead = GetPacketHeaderOverhead(NOT_IN_FEC_GROUP)
+                          + GetEncryptionOverhead();
+  for (size_t i = overhead; i < overhead + 100; ++i) {
+    creator_.set_max_packet_length(i);
+    const bool should_have_room = i > overhead + GetStreamFrameOverhead(
+        NOT_IN_FEC_GROUP);
+    ASSERT_EQ(should_have_room, creator_.HasRoomForStreamFrame(
+                                    kClientDataStreamId1, kOffset));
+    if (should_have_room) {
+      QuicFrame frame;
+      size_t bytes_consumed = creator_.CreateStreamFrame(
+          kClientDataStreamId1, MakeIOVector("testdata"), kOffset, false,
+          &frame);
+      EXPECT_LT(0u, bytes_consumed);
+      ASSERT_TRUE(creator_.AddSavedFrame(frame));
+      SerializedPacket serialized_packet = creator_.SerializePacket();
+      ASSERT_TRUE(serialized_packet.packet);
+      delete serialized_packet.packet;
+      delete serialized_packet.retransmittable_frames;
+    }
+  }
+}
+
+TEST_P(QuicPacketCreatorTest, StreamFrameConsumption) {
+  // Compute the total overhead for a single frame in packet.
+  const size_t overhead = GetPacketHeaderOverhead(NOT_IN_FEC_GROUP)
+      + GetEncryptionOverhead() + GetStreamFrameOverhead(NOT_IN_FEC_GROUP);
+  size_t capacity = kDefaultMaxPacketSize - overhead;
+  // Now, test various sizes around this size.
+  for (int delta = -5; delta <= 5; ++delta) {
+    string data(capacity + delta, 'A');
+    size_t bytes_free = delta > 0 ? 0 : 0 - delta;
+    QuicFrame frame;
+    size_t bytes_consumed = creator_.CreateStreamFrame(
+        kClientDataStreamId1, MakeIOVector(data), kOffset, false, &frame);
+    EXPECT_EQ(capacity - bytes_free, bytes_consumed);
+
+    ASSERT_TRUE(creator_.AddSavedFrame(frame));
+    // BytesFree() returns bytes available for the next frame, which will
+    // be two bytes smaller since the stream frame would need to be grown.
+    EXPECT_EQ(2u, creator_.ExpansionOnNewFrame());
+    size_t expected_bytes_free = bytes_free < 3 ? 0 : bytes_free - 2;
+    EXPECT_EQ(expected_bytes_free, creator_.BytesFree()) << "delta: " << delta;
+    SerializedPacket serialized_packet = creator_.SerializePacket();
+    ASSERT_TRUE(serialized_packet.packet);
+    delete serialized_packet.packet;
+    delete serialized_packet.retransmittable_frames;
+  }
+}
+
+TEST_P(QuicPacketCreatorTest, StreamFrameConsumptionWithFec) {
+  // Enable FEC protection, and send FEC packet every 6 packets.
+  EXPECT_TRUE(SwitchFecProtectionOn(6));
+  // Compute the total overhead for a single frame in packet.
+  const size_t overhead = GetPacketHeaderOverhead(IN_FEC_GROUP)
+      + GetEncryptionOverhead() + GetStreamFrameOverhead(IN_FEC_GROUP);
+  size_t capacity = kDefaultMaxPacketSize - overhead;
+  // Now, test various sizes around this size.
+  for (int delta = -5; delta <= 5; ++delta) {
+    string data(capacity + delta, 'A');
+    size_t bytes_free = delta > 0 ? 0 : 0 - delta;
+    QuicFrame frame;
+    size_t bytes_consumed = creator_.CreateStreamFrame(
+        kClientDataStreamId1, MakeIOVector(data), kOffset, false, &frame);
+    EXPECT_EQ(capacity - bytes_free, bytes_consumed);
+
+    ASSERT_TRUE(creator_.AddSavedFrame(frame));
+    // BytesFree() returns bytes available for the next frame. Since stream
+    // frame does not grow for FEC protected packets, this should be the same
+    // as bytes_free (bound by 0).
+    EXPECT_EQ(0u, creator_.ExpansionOnNewFrame());
+    size_t expected_bytes_free = bytes_free > 0 ? bytes_free : 0;
+    EXPECT_EQ(expected_bytes_free, creator_.BytesFree()) << "delta: " << delta;
+    SerializedPacket serialized_packet = creator_.SerializePacket();
+    ASSERT_TRUE(serialized_packet.packet);
+    delete serialized_packet.packet;
+    delete serialized_packet.retransmittable_frames;
+  }
+}
+
+TEST_P(QuicPacketCreatorTest, CryptoStreamFramePacketPadding) {
+  // Compute the total overhead for a single frame in packet.
+  const size_t overhead = GetPacketHeaderOverhead(NOT_IN_FEC_GROUP)
+      + GetEncryptionOverhead() + GetStreamFrameOverhead(NOT_IN_FEC_GROUP);
+  ASSERT_GT(kMaxPacketSize, overhead);
+  size_t capacity = kDefaultMaxPacketSize - overhead;
+  // Now, test various sizes around this size.
+  for (int delta = -5; delta <= 5; ++delta) {
+    string data(capacity + delta, 'A');
+    size_t bytes_free = delta > 0 ? 0 : 0 - delta;
+
+    QuicFrame frame;
+    size_t bytes_consumed = creator_.CreateStreamFrame(
+        kCryptoStreamId, MakeIOVector(data), kOffset, false, &frame);
+    EXPECT_LT(0u, bytes_consumed);
+    ASSERT_TRUE(creator_.AddSavedFrame(frame));
+    SerializedPacket serialized_packet = creator_.SerializePacket();
+    ASSERT_TRUE(serialized_packet.packet);
+    // If there is not enough space in the packet to fit a padding frame
+    // (1 byte) and to expand the stream frame (another 2 bytes) the packet
+    // will not be padded.
+    if (bytes_free < 3) {
+      EXPECT_EQ(client_framer_.GetMaxPlaintextSize(kDefaultMaxPacketSize)
+                - bytes_free, serialized_packet.packet->length());
+    } else {
+      EXPECT_EQ(client_framer_.GetMaxPlaintextSize(kDefaultMaxPacketSize),
+                serialized_packet.packet->length());
+    }
+    delete serialized_packet.packet;
+    delete serialized_packet.retransmittable_frames;
+  }
+}
+
+TEST_P(QuicPacketCreatorTest, NonCryptoStreamFramePacketNonPadding) {
+  // Compute the total overhead for a single frame in packet.
+  const size_t overhead = GetPacketHeaderOverhead(NOT_IN_FEC_GROUP)
+      + GetEncryptionOverhead() + GetStreamFrameOverhead(NOT_IN_FEC_GROUP);
+  ASSERT_GT(kDefaultMaxPacketSize, overhead);
+  size_t capacity = kDefaultMaxPacketSize - overhead;
+  // Now, test various sizes around this size.
+  for (int delta = -5; delta <= 5; ++delta) {
+    string data(capacity + delta, 'A');
+    size_t bytes_free = delta > 0 ? 0 : 0 - delta;
+
+    QuicFrame frame;
+    size_t bytes_consumed = creator_.CreateStreamFrame(
+        kClientDataStreamId1, MakeIOVector(data), kOffset, false, &frame);
+    EXPECT_LT(0u, bytes_consumed);
+    ASSERT_TRUE(creator_.AddSavedFrame(frame));
+    SerializedPacket serialized_packet = creator_.SerializePacket();
+    ASSERT_TRUE(serialized_packet.packet);
+    if (bytes_free > 0) {
+      EXPECT_EQ(client_framer_.GetMaxPlaintextSize(kDefaultMaxPacketSize)
+                - bytes_free, serialized_packet.packet->length());
+    } else {
+      EXPECT_EQ(client_framer_.GetMaxPlaintextSize(kDefaultMaxPacketSize),
+                serialized_packet.packet->length());
+    }
+    delete serialized_packet.packet;
+    delete serialized_packet.retransmittable_frames;
+  }
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeVersionNegotiationPacket) {
+  QuicFramerPeer::SetIsServer(&client_framer_, true);
+  QuicVersionVector versions;
+  versions.push_back(test::QuicVersionMax());
+  scoped_ptr<QuicEncryptedPacket> encrypted(
+      creator_.SerializeVersionNegotiationPacket(versions));
+
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnVersionNegotiationPacket(_));
+  }
+  QuicFramerPeer::SetIsServer(&client_framer_, false);
+  client_framer_.ProcessPacket(*encrypted);
+}
+
+TEST_P(QuicPacketCreatorTest, UpdatePacketSequenceNumberLengthLeastAwaiting) {
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  size_t max_packets_per_fec_group = 10;
+  creator_.set_max_packets_per_fec_group(max_packets_per_fec_group);
+  creator_.set_sequence_number(64 - max_packets_per_fec_group);
+  creator_.UpdateSequenceNumberLength(2, 10000);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.set_sequence_number(64 * 256 - max_packets_per_fec_group);
+  creator_.UpdateSequenceNumberLength(2, 10000);
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.set_sequence_number(64 * 256 * 256 - max_packets_per_fec_group);
+  creator_.UpdateSequenceNumberLength(2, 10000);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.set_sequence_number(
+      GG_UINT64_C(64) * 256 * 256 * 256 * 256 - max_packets_per_fec_group);
+  creator_.UpdateSequenceNumberLength(2, 10000);
+  EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+}
+
+TEST_P(QuicPacketCreatorTest, UpdatePacketSequenceNumberLengthBandwidth) {
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.UpdateSequenceNumberLength(1, 10000);
+  EXPECT_EQ(PACKET_1BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.UpdateSequenceNumberLength(1, 10000 * 256);
+  EXPECT_EQ(PACKET_2BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.UpdateSequenceNumberLength(1, 10000 * 256 * 256);
+  EXPECT_EQ(PACKET_4BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+
+  creator_.UpdateSequenceNumberLength(
+      1, GG_UINT64_C(1000) * 256 * 256 * 256 * 256);
+  EXPECT_EQ(PACKET_6BYTE_SEQUENCE_NUMBER,
+            creator_.next_sequence_number_length());
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrameWithNotifier) {
+  // Ensure that if CreateStreamFrame does not consume any data (e.g. a FIN only
+  // frame) then any QuicAckNotifier that is passed in still gets attached to
+  // the frame.
+  scoped_refptr<MockAckNotifierDelegate> delegate(new MockAckNotifierDelegate);
+  QuicAckNotifier notifier(delegate.get());
+  QuicFrame frame;
+  IOVector empty_iovector;
+  bool fin = true;
+  size_t consumed_bytes = creator_.CreateStreamFrameWithNotifier(
+      1u, empty_iovector, 0u, fin, &notifier, &frame);
+  EXPECT_EQ(0u, consumed_bytes);
+  EXPECT_EQ(&notifier, frame.stream_frame->notifier);
+  delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeFrame) {
+  if (!GetParam().version_serialization) {
+    creator_.StopSendingVersion();
+  }
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+  SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+  delete frames_[0].stream_frame;
+
+  QuicPacketHeader header;
+  {
+    InSequence s;
+    EXPECT_CALL(framer_visitor_, OnPacket());
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_));
+    EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_));
+    EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_));
+    EXPECT_CALL(framer_visitor_, OnPacketHeader(_)).WillOnce(
+        DoAll(SaveArg<0>(&header), Return(true)));
+    EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+    EXPECT_CALL(framer_visitor_, OnPacketComplete());
+  }
+  ProcessPacket(serialized.packet);
+  EXPECT_EQ(GetParam().version_serialization,
+            header.public_header.version_flag);
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrameTooLarge) {
+  if (!GetParam().version_serialization) {
+    creator_.StopSendingVersion();
+  }
+  // A string larger than fits into a frame.
+  size_t payload_length;
+  creator_.set_max_packet_length(GetPacketLengthForOneStream(
+      client_framer_.version(),
+      QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+      PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP, &payload_length));
+  QuicFrame frame;
+  const string too_long_payload(payload_length * 2, 'a');
+  size_t consumed = creator_.CreateStreamFrame(
+      1u, MakeIOVector(too_long_payload), 0u, true, &frame);
+  EXPECT_EQ(payload_length, consumed);
+  const string payload(payload_length, 'a');
+  CheckStreamFrame(frame, 1u, payload, 0u, false);
+  delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, AddFrameAndSerialize) {
+  if (!GetParam().version_serialization) {
+    creator_.StopSendingVersion();
+  }
+  const size_t max_plaintext_size =
+      client_framer_.GetMaxPlaintextSize(creator_.max_packet_length());
+  EXPECT_FALSE(creator_.HasPendingFrames());
+  EXPECT_EQ(max_plaintext_size -
+            GetPacketHeaderSize(
+                creator_.connection_id_length(),
+                QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+                PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+            creator_.BytesFree());
+
+  // Add a variety of frame types and then a padding frame.
+  QuicAckFrame ack_frame(MakeAckFrame(0u));
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  QuicCongestionFeedbackFrame congestion_feedback;
+  congestion_feedback.type = kTCP;
+  congestion_feedback.tcp.receive_window = 0x4030;
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&congestion_feedback)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  QuicFrame frame;
+  size_t consumed = creator_.CreateStreamFrame(
+      1u, MakeIOVector("test"), 0u, false, &frame);
+  EXPECT_EQ(4u, consumed);
+  ASSERT_TRUE(frame.stream_frame);
+  EXPECT_TRUE(creator_.AddSavedFrame(frame));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  QuicPaddingFrame padding_frame;
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&padding_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+  EXPECT_EQ(0u, creator_.BytesFree());
+
+  EXPECT_FALSE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+
+  // Ensure the packet is successfully created.
+  SerializedPacket serialized = creator_.SerializePacket();
+  ASSERT_TRUE(serialized.packet);
+  delete serialized.packet;
+  ASSERT_TRUE(serialized.retransmittable_frames);
+  RetransmittableFrames* retransmittable = serialized.retransmittable_frames;
+  ASSERT_EQ(1u, retransmittable->frames().size());
+  EXPECT_EQ(STREAM_FRAME, retransmittable->frames()[0].type);
+  ASSERT_TRUE(retransmittable->frames()[0].stream_frame);
+  delete serialized.retransmittable_frames;
+
+  EXPECT_FALSE(creator_.HasPendingFrames());
+  EXPECT_EQ(max_plaintext_size -
+            GetPacketHeaderSize(
+                creator_.connection_id_length(),
+                QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+                PACKET_1BYTE_SEQUENCE_NUMBER,
+                NOT_IN_FEC_GROUP),
+            creator_.BytesFree());
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeTruncatedAckFrameWithLargePacketSize) {
+  if (!GetParam().version_serialization) {
+    creator_.StopSendingVersion();
+  }
+  creator_.set_max_packet_length(kMaxPacketSize);
+  const size_t max_plaintext_size =
+      client_framer_.GetMaxPlaintextSize(creator_.max_packet_length());
+
+  // Serialized length of ack frame with 2000 nack ranges should be limited by
+  // the number of nack ranges that can be fit in an ack frame.
+  QuicAckFrame ack_frame = MakeAckFrameWithNackRanges(2000u, 0u);
+  size_t frame_len = client_framer_.GetSerializedFrameLength(
+      QuicFrame(&ack_frame), creator_.BytesFree(), true, true,
+      NOT_IN_FEC_GROUP, PACKET_1BYTE_SEQUENCE_NUMBER);
+  EXPECT_GT(creator_.BytesFree(), frame_len);
+  EXPECT_GT(max_plaintext_size, creator_.PacketSize());
+
+  // Add ack frame to creator.
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+  EXPECT_GT(max_plaintext_size, creator_.PacketSize());
+  EXPECT_LT(0u, creator_.BytesFree());
+
+  // Make sure that an additional stream frame can be added to the packet.
+  QuicFrame stream_frame;
+  size_t consumed = creator_.CreateStreamFrame(
+      2u, MakeIOVector("test"), 0u, false, &stream_frame);
+  EXPECT_EQ(4u, consumed);
+  ASSERT_TRUE(stream_frame.stream_frame);
+  EXPECT_TRUE(creator_.AddSavedFrame(stream_frame));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+
+  // Ensure the packet is successfully created, and the packet size estimate
+  // matches the serialized packet length.
+  EXPECT_CALL(entropy_calculator_,
+             EntropyHash(_)).WillOnce(testing::Return(0));
+  size_t est_packet_size = creator_.PacketSize();
+  SerializedPacket serialized = creator_.SerializePacket();
+  ASSERT_TRUE(serialized.packet);
+  EXPECT_EQ(est_packet_size, serialized.packet->length());
+  delete serialized.retransmittable_frames;
+  delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, SerializeTruncatedAckFrameWithSmallPacketSize) {
+  if (!GetParam().version_serialization) {
+    creator_.StopSendingVersion();
+  }
+  creator_.set_max_packet_length(500u);
+
+  const size_t max_plaintext_size =
+      client_framer_.GetMaxPlaintextSize(creator_.max_packet_length());
+  EXPECT_EQ(max_plaintext_size - creator_.PacketSize(), creator_.BytesFree());
+
+  // Serialized length of ack frame with 2000 nack ranges should be limited by
+  // the packet size.
+  QuicAckFrame ack_frame = MakeAckFrameWithNackRanges(2000u, 0u);
+  size_t frame_len = client_framer_.GetSerializedFrameLength(
+      QuicFrame(&ack_frame), creator_.BytesFree(), true, true,
+      NOT_IN_FEC_GROUP, PACKET_1BYTE_SEQUENCE_NUMBER);
+  EXPECT_EQ(creator_.BytesFree(), frame_len);
+
+  // Add ack frame to creator.
+  EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+  EXPECT_TRUE(creator_.HasPendingFrames());
+  EXPECT_EQ(max_plaintext_size, creator_.PacketSize());
+  EXPECT_EQ(0u, creator_.BytesFree());
+
+  // Ensure the packet is successfully created, and the packet size estimate
+  // may not match the serialized packet length.
+  EXPECT_CALL(entropy_calculator_,
+             EntropyHash(_)).WillOnce(Return(0));
+  size_t est_packet_size = creator_.PacketSize();
+  SerializedPacket serialized = creator_.SerializePacket();
+  ASSERT_TRUE(serialized.packet);
+  EXPECT_GE(est_packet_size, serialized.packet->length());
+  delete serialized.packet;
+}
+
+
+TEST_P(QuicPacketCreatorTest, EntropyFlag) {
+  frames_.push_back(QuicFrame(new QuicStreamFrame(0u, false, 0u, IOVector())));
+
+  for (int i = 0; i < 2; ++i) {
+    for (int j = 0; j < 64; ++j) {
+      SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+      // Verify both BoolSource and hash algorithm.
+      bool expected_rand_bool =
+          (mock_random_.RandUint64() & (GG_UINT64_C(1) << j)) != 0;
+      bool observed_rand_bool =
+          (serialized.entropy_hash & (1 << ((j+1) % 8))) != 0;
+      uint8 rest_of_hash = serialized.entropy_hash & ~(1 << ((j+1) % 8));
+      EXPECT_EQ(expected_rand_bool, observed_rand_bool);
+      EXPECT_EQ(0, rest_of_hash);
+      delete serialized.packet;
+    }
+    // After 64 calls, BoolSource will refresh the bucket - make sure it does.
+    mock_random_.ChangeValue();
+  }
+
+  delete frames_[0].stream_frame;
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_packet_generator.cc b/net/quic/quic_packet_generator.cc
new file mode 100644
index 0000000..11a45cc
--- /dev/null
+++ b/net/quic/quic_packet_generator.cc
@@ -0,0 +1,391 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_packet_generator.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+// We want to put some space between a protected packet and the FEC packet to
+// avoid losing them both within the same loss episode. On the other hand,
+// we expect to be able to recover from any loss in about an RTT.
+// We resolve this tradeoff by sending an FEC packet atmost half an RTT,
+// or equivalently, half a cwnd, after the first protected packet. Since we
+// don't want to delay an FEC packet past half an RTT, we set the max FEC
+// group size to be half the current congestion window.
+const float kCongestionWindowMultiplierForFecGroupSize = 0.5;
+
+}  // namespace
+
+class QuicAckNotifier;
+
+QuicPacketGenerator::QuicPacketGenerator(QuicConnectionId connection_id,
+                                         QuicFramer* framer,
+                                         QuicRandom* random_generator,
+                                         DelegateInterface* delegate)
+    : delegate_(delegate),
+      debug_delegate_(nullptr),
+      packet_creator_(connection_id, framer, random_generator),
+      batch_mode_(false),
+      should_fec_protect_(false),
+      should_send_ack_(false),
+      should_send_feedback_(false),
+      should_send_stop_waiting_(false) {}
+
+QuicPacketGenerator::~QuicPacketGenerator() {
+  for (QuicFrames::iterator it = queued_control_frames_.begin();
+       it != queued_control_frames_.end(); ++it) {
+    switch (it->type) {
+      case PADDING_FRAME:
+        delete it->padding_frame;
+        break;
+      case STREAM_FRAME:
+        delete it->stream_frame;
+        break;
+      case ACK_FRAME:
+        delete it->ack_frame;
+        break;
+      case CONGESTION_FEEDBACK_FRAME:
+        delete it->congestion_feedback_frame;
+        break;
+      case RST_STREAM_FRAME:
+        delete it->rst_stream_frame;
+        break;
+      case CONNECTION_CLOSE_FRAME:
+        delete it->connection_close_frame;
+        break;
+      case GOAWAY_FRAME:
+        delete it->goaway_frame;
+        break;
+      case WINDOW_UPDATE_FRAME:
+        delete it->window_update_frame;
+        break;
+      case BLOCKED_FRAME:
+        delete it->blocked_frame;
+        break;
+      case STOP_WAITING_FRAME:
+        delete it->stop_waiting_frame;
+        break;
+      case PING_FRAME:
+        delete it->ping_frame;
+        break;
+      case NUM_FRAME_TYPES:
+        DCHECK(false) << "Cannot delete type: " << it->type;
+    }
+  }
+}
+
+// NetworkChangeVisitor method.
+void QuicPacketGenerator::OnCongestionWindowChange(
+    QuicByteCount congestion_window) {
+  packet_creator_.set_max_packets_per_fec_group(
+      static_cast<size_t>(kCongestionWindowMultiplierForFecGroupSize *
+                          congestion_window / kDefaultTCPMSS));
+}
+
+void QuicPacketGenerator::SetShouldSendAck(bool also_send_feedback,
+                                           bool also_send_stop_waiting) {
+  should_send_ack_ = true;
+  should_send_feedback_ = also_send_feedback;
+  should_send_stop_waiting_ = also_send_stop_waiting;
+  SendQueuedFrames(false);
+}
+
+void QuicPacketGenerator::SetShouldSendStopWaiting() {
+  should_send_stop_waiting_ = true;
+  SendQueuedFrames(false);
+}
+
+void QuicPacketGenerator::AddControlFrame(const QuicFrame& frame) {
+  queued_control_frames_.push_back(frame);
+  SendQueuedFrames(false);
+}
+
+QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id,
+                                                  const IOVector& data_to_write,
+                                                  QuicStreamOffset offset,
+                                                  bool fin,
+                                                  FecProtection fec_protection,
+                                                  QuicAckNotifier* notifier) {
+  IsHandshake handshake = id == kCryptoStreamId ? IS_HANDSHAKE : NOT_HANDSHAKE;
+  // To make reasoning about crypto frames easier, we don't combine them with
+  // other retransmittable frames in a single packet.
+  const bool flush = handshake == IS_HANDSHAKE &&
+      packet_creator_.HasPendingRetransmittableFrames();
+  SendQueuedFrames(flush);
+
+  size_t total_bytes_consumed = 0;
+  bool fin_consumed = false;
+
+  if (!packet_creator_.HasRoomForStreamFrame(id, offset)) {
+    SerializeAndSendPacket();
+  }
+
+  if (fec_protection == MUST_FEC_PROTECT) {
+    MaybeStartFecProtection();
+  }
+
+  IOVector data = data_to_write;
+  size_t data_size = data.TotalBufferSize();
+  while (delegate_->ShouldGeneratePacket(NOT_RETRANSMISSION,
+                                         HAS_RETRANSMITTABLE_DATA, handshake)) {
+    QuicFrame frame;
+    size_t bytes_consumed;
+    if (notifier != nullptr) {
+      // We want to track which packet this stream frame ends up in.
+      bytes_consumed = packet_creator_.CreateStreamFrameWithNotifier(
+          id, data, offset + total_bytes_consumed, fin, notifier, &frame);
+    } else {
+      bytes_consumed = packet_creator_.CreateStreamFrame(
+          id, data, offset + total_bytes_consumed, fin, &frame);
+    }
+    if (!AddFrame(frame)) {
+      LOG(DFATAL) << "Failed to add stream frame.";
+      // Inability to add a STREAM frame creates an unrecoverable hole in a
+      // the stream, so it's best to close the connection.
+      delegate_->CloseConnection(QUIC_INTERNAL_ERROR, false);
+      return QuicConsumedData(0, false);
+    }
+
+    total_bytes_consumed += bytes_consumed;
+    fin_consumed = fin && total_bytes_consumed == data_size;
+    data.Consume(bytes_consumed);
+    DCHECK(data.Empty() || packet_creator_.BytesFree() == 0u);
+
+    // TODO(ianswett): Restore packet reordering.
+    if (!InBatchMode() || !packet_creator_.HasRoomForStreamFrame(id, offset)) {
+      SerializeAndSendPacket();
+    }
+
+    if (data.Empty()) {
+      // We're done writing the data. Exit the loop.
+      // We don't make this a precondition because we could have 0 bytes of data
+      // if we're simply writing a fin.
+      if (fec_protection == MUST_FEC_PROTECT) {
+        // Turn off FEC protection when we're done writing protected data.
+        DVLOG(1) << "Turning FEC protection OFF";
+        should_fec_protect_ = false;
+      }
+      break;
+    }
+  }
+
+  // Don't allow the handshake to be bundled with other retransmittable frames.
+  if (handshake == IS_HANDSHAKE) {
+    SendQueuedFrames(true);
+  }
+
+  // Try to close FEC group since we've either run out of data to send or we're
+  // blocked. If not in batch mode, force close the group.
+  // TODO(jri): This method should be called with flush=false here
+  // once the timer-based FEC sending is done, to separate FEC sending from
+  // the end of batch operations.
+  MaybeSendFecPacketAndCloseGroup(!InBatchMode());
+
+  DCHECK(InBatchMode() || !packet_creator_.HasPendingFrames());
+  return QuicConsumedData(total_bytes_consumed, fin_consumed);
+}
+
+bool QuicPacketGenerator::CanSendWithNextPendingFrameAddition() const {
+  DCHECK(HasPendingFrames());
+  HasRetransmittableData retransmittable =
+      (should_send_ack_ || should_send_feedback_ || should_send_stop_waiting_)
+      ? NO_RETRANSMITTABLE_DATA : HAS_RETRANSMITTABLE_DATA;
+  if (retransmittable == HAS_RETRANSMITTABLE_DATA) {
+      DCHECK(!queued_control_frames_.empty());  // These are retransmittable.
+  }
+  return delegate_->ShouldGeneratePacket(NOT_RETRANSMISSION, retransmittable,
+                                         NOT_HANDSHAKE);
+}
+
+void QuicPacketGenerator::SendQueuedFrames(bool flush) {
+  // Only add pending frames if we are SURE we can then send the whole packet.
+  while (HasPendingFrames() &&
+         (flush || CanSendWithNextPendingFrameAddition())) {
+    if (!AddNextPendingFrame()) {
+      // Packet was full, so serialize and send it.
+      SerializeAndSendPacket();
+    }
+  }
+
+  if (!InBatchMode() || flush) {
+    if (packet_creator_.HasPendingFrames()) {
+      SerializeAndSendPacket();
+    }
+    // Ensure the FEC group is closed at the end of this method unless other
+    // writes are pending.
+    MaybeSendFecPacketAndCloseGroup(true);
+  }
+}
+
+void QuicPacketGenerator::MaybeStartFecProtection() {
+  if (!packet_creator_.IsFecEnabled()) {
+    return;
+  }
+  DVLOG(1) << "Turning FEC protection ON";
+  should_fec_protect_ = true;
+  if (packet_creator_.IsFecProtected()) {
+    // Only start creator's FEC protection if not already on.
+    return;
+  }
+  if (HasQueuedFrames()) {
+    // TODO(jri): This currently requires that the generator flush out any
+    // pending frames when FEC protection is turned on. If current packet can be
+    // converted to an FEC protected packet, do it. This will require the
+    // generator to check if the resulting expansion still allows the incoming
+    // frame to be added to the packet.
+    SendQueuedFrames(true);
+  }
+  packet_creator_.StartFecProtectingPackets();
+  DCHECK(packet_creator_.IsFecProtected());
+}
+
+void QuicPacketGenerator::MaybeSendFecPacketAndCloseGroup(bool force) {
+  if (!packet_creator_.IsFecProtected() ||
+      packet_creator_.HasPendingFrames() ||
+      !packet_creator_.ShouldSendFec(force)) {
+    return;
+  }
+  // TODO(jri): SerializeFec can return a NULL packet, and this should
+  // cause an early return, with a call to delegate_->OnPacketGenerationError.
+  SerializedPacket serialized_fec = packet_creator_.SerializeFec();
+  DCHECK(serialized_fec.packet);
+  delegate_->OnSerializedPacket(serialized_fec);
+  // Turn FEC protection off if creator's protection is on and the creator
+  // does not have an open FEC group.
+  // Note: We only wait until the frames queued in the creator are flushed;
+  // pending frames in the generator will not keep us from turning FEC off.
+  if (!should_fec_protect_ && !packet_creator_.IsFecGroupOpen()) {
+    packet_creator_.StopFecProtectingPackets();
+    DCHECK(!packet_creator_.IsFecProtected());
+  }
+}
+
+bool QuicPacketGenerator::InBatchMode() {
+  return batch_mode_;
+}
+
+void QuicPacketGenerator::StartBatchOperations() {
+  batch_mode_ = true;
+}
+
+void QuicPacketGenerator::FinishBatchOperations() {
+  batch_mode_ = false;
+  SendQueuedFrames(false);
+}
+
+void QuicPacketGenerator::FlushAllQueuedFrames() {
+  SendQueuedFrames(true);
+}
+
+bool QuicPacketGenerator::HasQueuedFrames() const {
+  return packet_creator_.HasPendingFrames() || HasPendingFrames();
+}
+
+bool QuicPacketGenerator::HasPendingFrames() const {
+  return should_send_ack_ || should_send_feedback_ ||
+      should_send_stop_waiting_ || !queued_control_frames_.empty();
+}
+
+bool QuicPacketGenerator::AddNextPendingFrame() {
+  if (should_send_ack_) {
+    pending_ack_frame_.reset(delegate_->CreateAckFrame());
+    // If we can't this add the frame now, then we still need to do so later.
+    should_send_ack_ = !AddFrame(QuicFrame(pending_ack_frame_.get()));
+    // Return success if we have cleared out this flag (i.e., added the frame).
+    // If we still need to send, then the frame is full, and we have failed.
+    return !should_send_ack_;
+  }
+
+  if (should_send_feedback_) {
+    pending_feedback_frame_.reset(delegate_->CreateFeedbackFrame());
+    // If we can't this add the frame now, then we still need to do so later.
+    should_send_feedback_ = !AddFrame(QuicFrame(pending_feedback_frame_.get()));
+    // Return success if we have cleared out this flag (i.e., added the frame).
+    // If we still need to send, then the frame is full, and we have failed.
+    return !should_send_feedback_;
+  }
+
+  if (should_send_stop_waiting_) {
+    pending_stop_waiting_frame_.reset(delegate_->CreateStopWaitingFrame());
+    // If we can't this add the frame now, then we still need to do so later.
+    should_send_stop_waiting_ =
+        !AddFrame(QuicFrame(pending_stop_waiting_frame_.get()));
+    // Return success if we have cleared out this flag (i.e., added the frame).
+    // If we still need to send, then the frame is full, and we have failed.
+    return !should_send_stop_waiting_;
+  }
+
+  LOG_IF(DFATAL, queued_control_frames_.empty())
+      << "AddNextPendingFrame called with no queued control frames.";
+  if (!AddFrame(queued_control_frames_.back())) {
+    // Packet was full.
+    return false;
+  }
+  queued_control_frames_.pop_back();
+  return true;
+}
+
+bool QuicPacketGenerator::AddFrame(const QuicFrame& frame) {
+  bool success = packet_creator_.AddSavedFrame(frame);
+  if (success && debug_delegate_) {
+    debug_delegate_->OnFrameAddedToPacket(frame);
+  }
+  return success;
+}
+
+void QuicPacketGenerator::SerializeAndSendPacket() {
+  SerializedPacket serialized_packet = packet_creator_.SerializePacket();
+  DCHECK(serialized_packet.packet);
+  delegate_->OnSerializedPacket(serialized_packet);
+  MaybeSendFecPacketAndCloseGroup(false);
+}
+
+void QuicPacketGenerator::StopSendingVersion() {
+  packet_creator_.StopSendingVersion();
+}
+
+QuicPacketSequenceNumber QuicPacketGenerator::sequence_number() const {
+  return packet_creator_.sequence_number();
+}
+
+size_t QuicPacketGenerator::max_packet_length() const {
+  return packet_creator_.max_packet_length();
+}
+
+void QuicPacketGenerator::set_max_packet_length(size_t length) {
+  packet_creator_.set_max_packet_length(length);
+}
+
+QuicEncryptedPacket* QuicPacketGenerator::SerializeVersionNegotiationPacket(
+    const QuicVersionVector& supported_versions) {
+  return packet_creator_.SerializeVersionNegotiationPacket(supported_versions);
+}
+
+SerializedPacket QuicPacketGenerator::ReserializeAllFrames(
+    const QuicFrames& frames,
+    QuicSequenceNumberLength original_length) {
+  return packet_creator_.ReserializeAllFrames(frames, original_length);
+}
+
+void QuicPacketGenerator::UpdateSequenceNumberLength(
+      QuicPacketSequenceNumber least_packet_awaited_by_peer,
+      QuicByteCount congestion_window) {
+  return packet_creator_.UpdateSequenceNumberLength(
+      least_packet_awaited_by_peer, congestion_window);
+}
+
+void QuicPacketGenerator::set_encryption_level(EncryptionLevel level) {
+  packet_creator_.set_encryption_level(level);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_packet_generator.h b/net/quic/quic_packet_generator.h
new file mode 100644
index 0000000..44a6ddf
--- /dev/null
+++ b/net/quic/quic_packet_generator.h
@@ -0,0 +1,247 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Responsible for generating packets on behalf of a QuicConnection.
+// Packets are serialized just-in-time.  Control frames are queued.
+// Ack and Feedback frames will be requested from the Connection
+// just-in-time.  When a packet needs to be sent, the Generator
+// will serialize a packet and pass it to QuicConnection::SendOrQueuePacket()
+//
+// The Generator's mode of operation is controlled by two conditions:
+//
+// 1) Is the Delegate writable?
+//
+// If the Delegate is not writable, then no operations will cause
+// a packet to be serialized.  In particular:
+// * SetShouldSendAck will simply record that an ack is to be sent.
+// * AddControlFrame will enqueue the control frame.
+// * ConsumeData will do nothing.
+//
+// If the Delegate is writable, then the behavior depends on the second
+// condition:
+//
+// 2) Is the Generator in batch mode?
+//
+// If the Generator is NOT in batch mode, then each call to a write
+// operation will serialize one or more packets.  The contents will
+// include any previous queued frames.  If an ack should be sent
+// but has not been sent, then the Delegate will be asked to create
+// an Ack frame which will then be included in the packet.  When
+// the write call completes, the current packet will be serialized
+// and sent to the Delegate, even if it is not full.
+//
+// If the Generator is in batch mode, then each write operation will
+// add data to the "current" packet.  When the current packet becomes
+// full, it will be serialized and sent to the packet.  When batch
+// mode is ended via |FinishBatchOperations|, the current packet
+// will be serialzied, even if it is not full.
+//
+// FEC behavior also depends on batch mode.  In batch mode, FEC packets
+// will be sent after |max_packets_per_group| have been sent, as well
+// as after batch operations are complete.  When not in batch mode,
+// an FEC packet will be sent after each write call completes.
+//
+// TODO(rch): This behavior should probably be tuned.  When not in batch
+// mode, we should probably set a timer so that several independent
+// operations can be grouped into the same FEC group.
+//
+// When an FEC packet is generated, it will be send to the Delegate,
+// even if the Delegate has become unwritable after handling the
+// data packet immediately proceeding the FEC packet.
+
+#ifndef NET_QUIC_QUIC_PACKET_GENERATOR_H_
+#define NET_QUIC_QUIC_PACKET_GENERATOR_H_
+
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_sent_packet_manager.h"
+#include "net/quic/quic_types.h"
+
+namespace net {
+
+namespace test {
+class QuicPacketGeneratorPeer;
+}  // namespace test
+
+class QuicAckNotifier;
+
+class NET_EXPORT_PRIVATE QuicPacketGenerator {
+ public:
+  class NET_EXPORT_PRIVATE DelegateInterface {
+   public:
+    virtual ~DelegateInterface() {}
+    virtual bool ShouldGeneratePacket(TransmissionType transmission_type,
+                                      HasRetransmittableData retransmittable,
+                                      IsHandshake handshake) = 0;
+    virtual QuicAckFrame* CreateAckFrame() = 0;
+    virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() = 0;
+    virtual QuicStopWaitingFrame* CreateStopWaitingFrame() = 0;
+    // Takes ownership of |packet.packet| and |packet.retransmittable_frames|.
+    virtual void OnSerializedPacket(const SerializedPacket& packet) = 0;
+    virtual void CloseConnection(QuicErrorCode error, bool from_peer) = 0;
+  };
+
+  // Interface which gets callbacks from the QuicPacketGenerator at interesting
+  // points.  Implementations must not mutate the state of the generator
+  // as a result of these callbacks.
+  class NET_EXPORT_PRIVATE DebugDelegate {
+   public:
+    virtual ~DebugDelegate() {}
+
+    // Called when a frame has been added to the current packet.
+    virtual void OnFrameAddedToPacket(const QuicFrame& frame) {}
+  };
+
+  QuicPacketGenerator(QuicConnectionId connection_id,
+                      QuicFramer* framer,
+                      QuicRandom* random_generator,
+                      DelegateInterface* delegate);
+
+  virtual ~QuicPacketGenerator();
+
+  // Called by the connection in the event of the congestion window changing.
+  void OnCongestionWindowChange(QuicByteCount congestion_window);
+
+  // Indicates that an ACK frame should be sent.  If |also_send_feedback| is
+  // true, then it also indicates a CONGESTION_FEEDBACK frame should be sent.
+  // If |also_send_stop_waiting| is true, then it also indicates that a
+  // STOP_WAITING frame should be sent as well.
+  // The contents of the frame(s) will be generated via a call to the delegates
+  // CreateAckFrame() and CreateFeedbackFrame() when the packet is serialized.
+  void SetShouldSendAck(bool also_send_feedback,
+                        bool also_send_stop_waiting);
+
+  // Indicates that a STOP_WAITING frame should be sent.
+  void SetShouldSendStopWaiting();
+
+  void AddControlFrame(const QuicFrame& frame);
+
+  // Given some data, may consume part or all of it and pass it to the
+  // packet creator to be serialized into packets. If not in batch
+  // mode, these packets will also be sent during this call. Also
+  // attaches a QuicAckNotifier to any created stream frames, which
+  // will be called once the frame is ACKed by the peer. The
+  // QuicAckNotifier is owned by the QuicConnection. |notifier| may
+  // be nullptr.
+  QuicConsumedData ConsumeData(QuicStreamId id,
+                               const IOVector& data,
+                               QuicStreamOffset offset,
+                               bool fin,
+                               FecProtection fec_protection,
+                               QuicAckNotifier* notifier);
+
+  // Indicates whether batch mode is currently enabled.
+  bool InBatchMode();
+  // Disables flushing.
+  void StartBatchOperations();
+  // Enables flushing and flushes queued data which can be sent.
+  void FinishBatchOperations();
+
+  // Flushes all queued frames, even frames which are not sendable.
+  void FlushAllQueuedFrames();
+
+  bool HasQueuedFrames() const;
+
+  // Makes the framer not serialize the protocol version in sent packets.
+  void StopSendingVersion();
+
+  // Creates a version negotiation packet which supports |supported_versions|.
+  // Caller owns the created  packet. Also, sets the entropy hash of the
+  // serialized packet to a random bool and returns that value as a member of
+  // SerializedPacket.
+  QuicEncryptedPacket* SerializeVersionNegotiationPacket(
+      const QuicVersionVector& supported_versions);
+
+
+  // Re-serializes frames with the original packet's sequence number length.
+  // Used for retransmitting packets to ensure they aren't too long.
+  // Caller must ensure that any open FEC group is closed before calling this
+  // method.
+  SerializedPacket ReserializeAllFrames(
+      const QuicFrames& frames,
+      QuicSequenceNumberLength original_length);
+
+  // Update the sequence number length to use in future packets as soon as it
+  // can be safely changed.
+  void UpdateSequenceNumberLength(
+      QuicPacketSequenceNumber least_packet_awaited_by_peer,
+      QuicByteCount congestion_window);
+
+  // Sets the encryption level that will be applied to new packets.
+  void set_encryption_level(EncryptionLevel level);
+
+  // Sequence number of the last created packet, or 0 if no packets have been
+  // created.
+  QuicPacketSequenceNumber sequence_number() const;
+
+  size_t max_packet_length() const;
+
+  void set_max_packet_length(size_t length);
+
+  void set_debug_delegate(DebugDelegate* debug_delegate) {
+    debug_delegate_ = debug_delegate;
+  }
+
+ private:
+  friend class test::QuicPacketGeneratorPeer;
+
+  // Turn on FEC protection for subsequent packets in the generator.
+  // If no FEC group is currently open in the creator, this method flushes any
+  // queued frames in the generator and in the creator, and it then turns FEC on
+  // in the creator. This method may be called with an open FEC group in the
+  // creator, in which case, only the generator's state is altered.
+  void MaybeStartFecProtection();
+
+  // Serializes and calls the delegate on an FEC packet if one was under
+  // construction in the creator. When |force| is false, it relies on the
+  // creator being ready to send an FEC packet, otherwise FEC packet is sent
+  // as long as one is under construction in the creator. Also tries to turn
+  // off FEC protection in the creator if it's off in the generator.
+  void MaybeSendFecPacketAndCloseGroup(bool force);
+
+  void SendQueuedFrames(bool flush);
+
+  // Test to see if we have pending ack, feedback, or control frames.
+  bool HasPendingFrames() const;
+  // Test to see if the addition of a pending frame (which might be
+  // retransmittable) would still allow the resulting packet to be sent now.
+  bool CanSendWithNextPendingFrameAddition() const;
+  // Add exactly one pending frame, preferring ack over feedback over control
+  // frames.
+  bool AddNextPendingFrame();
+
+  bool AddFrame(const QuicFrame& frame);
+
+  void SerializeAndSendPacket();
+
+  DelegateInterface* delegate_;
+  DebugDelegate* debug_delegate_;
+
+  QuicPacketCreator packet_creator_;
+  QuicFrames queued_control_frames_;
+
+  // True if batch mode is currently enabled.
+  bool batch_mode_;
+
+  // True if FEC protection is on. The creator may have an open FEC group even
+  // if this variable is false.
+  bool should_fec_protect_;
+
+  // Flags to indicate the need for just-in-time construction of a frame.
+  bool should_send_ack_;
+  bool should_send_feedback_;
+  bool should_send_stop_waiting_;
+  // If we put a non-retransmittable frame (namley ack or feedback frame) in
+  // this packet, then we have to hold a reference to it until we flush (and
+  // serialize it). Retransmittable frames are referenced elsewhere so that they
+  // can later be (optionally) retransmitted.
+  scoped_ptr<QuicAckFrame> pending_ack_frame_;
+  scoped_ptr<QuicCongestionFeedbackFrame> pending_feedback_frame_;
+  scoped_ptr<QuicStopWaitingFrame> pending_stop_waiting_frame_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicPacketGenerator);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_PACKET_GENERATOR_H_
diff --git a/net/quic/quic_packet_generator_test.cc b/net/quic/quic_packet_generator_test.cc
new file mode 100644
index 0000000..703fd64
--- /dev/null
+++ b/net/quic/quic_packet_generator_test.cc
@@ -0,0 +1,957 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_packet_generator.h"
+
+#include <string>
+
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/quic/test_tools/quic_packet_generator_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockDelegate : public QuicPacketGenerator::DelegateInterface {
+ public:
+  MockDelegate() {}
+  virtual ~MockDelegate() OVERRIDE {}
+
+  MOCK_METHOD3(ShouldGeneratePacket,
+               bool(TransmissionType transmission_type,
+                    HasRetransmittableData retransmittable,
+                    IsHandshake handshake));
+  MOCK_METHOD0(CreateAckFrame, QuicAckFrame*());
+  MOCK_METHOD0(CreateFeedbackFrame, QuicCongestionFeedbackFrame*());
+  MOCK_METHOD0(CreateStopWaitingFrame, QuicStopWaitingFrame*());
+  MOCK_METHOD1(OnSerializedPacket, void(const SerializedPacket& packet));
+  MOCK_METHOD2(CloseConnection, void(QuicErrorCode, bool));
+
+  void SetCanWriteAnything() {
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION, _, _))
+        .WillRepeatedly(Return(true));
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION,
+                                            NO_RETRANSMITTABLE_DATA, _))
+        .WillRepeatedly(Return(true));
+  }
+
+  void SetCanNotWrite() {
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION, _, _))
+        .WillRepeatedly(Return(false));
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION,
+                                            NO_RETRANSMITTABLE_DATA, _))
+        .WillRepeatedly(Return(false));
+  }
+
+  // Use this when only ack and feedback frames should be allowed to be written.
+  void SetCanWriteOnlyNonRetransmittable() {
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION, _, _))
+        .WillRepeatedly(Return(false));
+    EXPECT_CALL(*this, ShouldGeneratePacket(NOT_RETRANSMISSION,
+                                            NO_RETRANSMITTABLE_DATA, _))
+        .WillRepeatedly(Return(true));
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+// Simple struct for describing the contents of a packet.
+// Useful in conjunction with a SimpleQuicFrame for validating
+// that a packet contains the expected frames.
+struct PacketContents {
+  PacketContents()
+      : num_ack_frames(0),
+        num_connection_close_frames(0),
+        num_feedback_frames(0),
+        num_goaway_frames(0),
+        num_rst_stream_frames(0),
+        num_stop_waiting_frames(0),
+        num_stream_frames(0),
+        fec_group(0) {
+  }
+
+  size_t num_ack_frames;
+  size_t num_connection_close_frames;
+  size_t num_feedback_frames;
+  size_t num_goaway_frames;
+  size_t num_rst_stream_frames;
+  size_t num_stop_waiting_frames;
+  size_t num_stream_frames;
+
+  QuicFecGroupNumber fec_group;
+};
+
+}  // namespace
+
+class QuicPacketGeneratorTest : public ::testing::Test {
+ protected:
+  QuicPacketGeneratorTest()
+      : framer_(QuicSupportedVersions(), QuicTime::Zero(), false),
+        generator_(42, &framer_, &random_, &delegate_),
+        creator_(QuicPacketGeneratorPeer::GetPacketCreator(&generator_)),
+        packet_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet2_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet3_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet4_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet5_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet6_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr),
+        packet7_(0, PACKET_1BYTE_SEQUENCE_NUMBER, nullptr, 0, nullptr) {}
+
+  virtual ~QuicPacketGeneratorTest() OVERRIDE {
+    delete packet_.packet;
+    delete packet_.retransmittable_frames;
+    delete packet2_.packet;
+    delete packet2_.retransmittable_frames;
+    delete packet3_.packet;
+    delete packet3_.retransmittable_frames;
+    delete packet4_.packet;
+    delete packet4_.retransmittable_frames;
+    delete packet5_.packet;
+    delete packet5_.retransmittable_frames;
+    delete packet6_.packet;
+    delete packet6_.retransmittable_frames;
+    delete packet7_.packet;
+    delete packet7_.retransmittable_frames;
+  }
+
+  QuicAckFrame* CreateAckFrame() {
+    // TODO(rch): Initialize this so it can be verified later.
+    return new QuicAckFrame(MakeAckFrame(0));
+  }
+
+  QuicCongestionFeedbackFrame* CreateFeedbackFrame() {
+    QuicCongestionFeedbackFrame* frame = new QuicCongestionFeedbackFrame;
+    frame->type = kTCP;
+    frame->tcp.receive_window = 0x4030;
+    return frame;
+  }
+
+  QuicStopWaitingFrame* CreateStopWaitingFrame() {
+    QuicStopWaitingFrame* frame = new QuicStopWaitingFrame();
+    frame->entropy_hash = 0;
+    frame->least_unacked = 0;
+    return frame;
+  }
+
+  QuicRstStreamFrame* CreateRstStreamFrame() {
+    return new QuicRstStreamFrame(1, QUIC_STREAM_NO_ERROR, 0);
+  }
+
+  QuicGoAwayFrame* CreateGoAwayFrame() {
+    return new QuicGoAwayFrame(QUIC_NO_ERROR, 1, string());
+  }
+
+  void CheckPacketContains(const PacketContents& contents,
+                           const SerializedPacket& packet) {
+    size_t num_retransmittable_frames = contents.num_connection_close_frames +
+        contents.num_goaway_frames + contents.num_rst_stream_frames +
+        contents.num_stream_frames;
+    size_t num_frames = contents.num_feedback_frames + contents.num_ack_frames +
+        contents.num_stop_waiting_frames + num_retransmittable_frames;
+
+    if (num_retransmittable_frames == 0) {
+      ASSERT_TRUE(packet.retransmittable_frames == nullptr);
+    } else {
+      ASSERT_TRUE(packet.retransmittable_frames != nullptr);
+      EXPECT_EQ(num_retransmittable_frames,
+                packet.retransmittable_frames->frames().size());
+    }
+
+    ASSERT_TRUE(packet.packet != nullptr);
+    ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+    EXPECT_EQ(num_frames, simple_framer_.num_frames());
+    EXPECT_EQ(contents.num_ack_frames, simple_framer_.ack_frames().size());
+    EXPECT_EQ(contents.num_connection_close_frames,
+              simple_framer_.connection_close_frames().size());
+    EXPECT_EQ(contents.num_feedback_frames,
+              simple_framer_.feedback_frames().size());
+    EXPECT_EQ(contents.num_goaway_frames,
+              simple_framer_.goaway_frames().size());
+    EXPECT_EQ(contents.num_rst_stream_frames,
+              simple_framer_.rst_stream_frames().size());
+    EXPECT_EQ(contents.num_stream_frames,
+              simple_framer_.stream_frames().size());
+    EXPECT_EQ(contents.num_stop_waiting_frames,
+              simple_framer_.stop_waiting_frames().size());
+    EXPECT_EQ(contents.fec_group, simple_framer_.header().fec_group);
+  }
+
+  void CheckPacketHasSingleStreamFrame(const SerializedPacket& packet) {
+    ASSERT_TRUE(packet.retransmittable_frames != nullptr);
+    EXPECT_EQ(1u, packet.retransmittable_frames->frames().size());
+    ASSERT_TRUE(packet.packet != nullptr);
+    ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+    EXPECT_EQ(1u, simple_framer_.num_frames());
+    EXPECT_EQ(1u, simple_framer_.stream_frames().size());
+  }
+
+  void CheckPacketIsFec(const SerializedPacket& packet,
+                        QuicPacketSequenceNumber fec_group) {
+    ASSERT_TRUE(packet.retransmittable_frames == nullptr);
+    ASSERT_TRUE(packet.packet != nullptr);
+    ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+    EXPECT_TRUE(simple_framer_.header().fec_flag);
+    EXPECT_EQ(fec_group, simple_framer_.fec_data().fec_group);
+  }
+
+  IOVector CreateData(size_t len) {
+    data_array_.reset(new char[len]);
+    memset(data_array_.get(), '?', len);
+    IOVector data;
+    data.Append(data_array_.get(), len);
+    return data;
+  }
+
+  QuicFramer framer_;
+  MockRandom random_;
+  StrictMock<MockDelegate> delegate_;
+  QuicPacketGenerator generator_;
+  QuicPacketCreator* creator_;
+  SimpleQuicFramer simple_framer_;
+  SerializedPacket packet_;
+  SerializedPacket packet2_;
+  SerializedPacket packet3_;
+  SerializedPacket packet4_;
+  SerializedPacket packet5_;
+  SerializedPacket packet6_;
+  SerializedPacket packet7_;
+
+ private:
+  scoped_ptr<char[]> data_array_;
+};
+
+class MockDebugDelegate : public QuicPacketGenerator::DebugDelegate {
+ public:
+  MOCK_METHOD1(OnFrameAddedToPacket,
+               void(const QuicFrame&));
+};
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_NotWritable) {
+  delegate_.SetCanNotWrite();
+
+  generator_.SetShouldSendAck(false, false);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_WritableAndShouldNotFlush) {
+  StrictMock<MockDebugDelegate> debug_delegate;
+
+  generator_.set_debug_delegate(&debug_delegate);
+  delegate_.SetCanWriteOnlyNonRetransmittable();
+  generator_.StartBatchOperations();
+
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(debug_delegate, OnFrameAddedToPacket(_)).Times(1);
+
+  generator_.SetShouldSendAck(false, false);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_WritableAndShouldFlush) {
+  delegate_.SetCanWriteOnlyNonRetransmittable();
+
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+
+  generator_.SetShouldSendAck(false, false);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_ack_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest,
+       ShouldSendAckWithFeedback_WritableAndShouldNotFlush) {
+  delegate_.SetCanWriteOnlyNonRetransmittable();
+  generator_.StartBatchOperations();
+
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+      Return(CreateFeedbackFrame()));
+
+  generator_.SetShouldSendAck(true, false);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest,
+       ShouldSendAckWithFeedback_WritableAndShouldFlush) {
+  delegate_.SetCanWriteOnlyNonRetransmittable();
+
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+      Return(CreateFeedbackFrame()));
+  EXPECT_CALL(delegate_, CreateStopWaitingFrame()).WillOnce(
+      Return(CreateStopWaitingFrame()));
+
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+
+  generator_.SetShouldSendAck(true, true);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_ack_frames = 1;
+  contents.num_feedback_frames = 1;
+  contents.num_stop_waiting_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_NotWritable) {
+  delegate_.SetCanNotWrite();
+
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_OnlyAckWritable) {
+  delegate_.SetCanWriteOnlyNonRetransmittable();
+
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_WritableAndShouldNotFlush) {
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_NotWritableBatchThenFlush) {
+  delegate_.SetCanNotWrite();
+  generator_.StartBatchOperations();
+
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+  generator_.FinishBatchOperations();
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  generator_.FlushAllQueuedFrames();
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_rst_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_WritableAndShouldFlush) {
+  delegate_.SetCanWriteAnything();
+
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_rst_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_NotWritable) {
+  delegate_.SetCanNotWrite();
+
+  QuicConsumedData consumed = generator_.ConsumeData(
+      kHeadersStreamId, MakeIOVector("foo"), 2, true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(0u, consumed.bytes_consumed);
+  EXPECT_FALSE(consumed.fin_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldNotFlush) {
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  QuicConsumedData consumed = generator_.ConsumeData(
+      kHeadersStreamId, MakeIOVector("foo"), 2, true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(3u, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldFlush) {
+  delegate_.SetCanWriteAnything();
+
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  QuicConsumedData consumed = generator_.ConsumeData(
+      kHeadersStreamId, MakeIOVector("foo"), 2, true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(3u, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest,
+       ConsumeDataMultipleTimes_WritableAndShouldNotFlush) {
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  generator_.ConsumeData(kHeadersStreamId, MakeIOVector("foo"), 2, true,
+                         MAY_FEC_PROTECT, nullptr);
+  QuicConsumedData consumed = generator_.ConsumeData(
+      3, MakeIOVector("quux"), 7, false, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(4u, consumed.bytes_consumed);
+  EXPECT_FALSE(consumed.fin_consumed);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_BatchOperations) {
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  generator_.ConsumeData(kHeadersStreamId, MakeIOVector("foo"), 2, true,
+                         MAY_FEC_PROTECT, nullptr);
+  QuicConsumedData consumed = generator_.ConsumeData(
+      3, MakeIOVector("quux"), 7, false, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(4u, consumed.bytes_consumed);
+  EXPECT_FALSE(consumed.fin_consumed);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+
+  // Now both frames will be flushed out.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  generator_.FinishBatchOperations();
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_stream_frames = 2;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeDataFEC) {
+  delegate_.SetCanWriteAnything();
+
+  // Send FEC every two packets.
+  creator_->set_max_packets_per_fec_group(2);
+
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet4_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet5_));
+  }
+
+  // Send enough data to create 3 packets: two full and one partial. Send
+  // with MUST_FEC_PROTECT flag.
+  size_t data_len = 2 * kDefaultMaxPacketSize + 100;
+  QuicConsumedData consumed = generator_.ConsumeData(
+      3, CreateData(data_len), 0, true, MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  CheckPacketHasSingleStreamFrame(packet_);
+  CheckPacketHasSingleStreamFrame(packet2_);
+  CheckPacketIsFec(packet3_, 1);
+
+  CheckPacketHasSingleStreamFrame(packet4_);
+  CheckPacketIsFec(packet5_, 4);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeDataSendsFecAtEnd) {
+  delegate_.SetCanWriteAnything();
+
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(6);
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+  }
+
+  // Send enough data to create 2 packets: one full and one partial. Send
+  // with MUST_FEC_PROTECT flag.
+  size_t data_len = 1 * kDefaultMaxPacketSize + 100;
+  QuicConsumedData consumed = generator_.ConsumeData(
+      3, CreateData(data_len), 0, true, MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  CheckPacketHasSingleStreamFrame(packet_);
+  CheckPacketHasSingleStreamFrame(packet2_);
+  CheckPacketIsFec(packet3_, 1);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_FramesPreviouslyQueued) {
+  // Set the packet size be enough for two stream frames with 0 stream offset,
+  // but not enough for a stream frame of 0 offset and one with non-zero offset.
+  size_t length =
+      NullEncrypter().GetCiphertextSize(0) +
+      GetPacketHeaderSize(creator_->connection_id_length(),
+                          true,
+                          creator_->next_sequence_number_length(),
+                          NOT_IN_FEC_GROUP) +
+      // Add an extra 3 bytes for the payload and 1 byte so BytesFree is larger
+      // than the GetMinStreamFrameSize.
+      QuicFramer::GetMinStreamFrameSize(1, 0, false, NOT_IN_FEC_GROUP) + 3 +
+      QuicFramer::GetMinStreamFrameSize(1, 0, true, NOT_IN_FEC_GROUP) + 1;
+  creator_->set_max_packet_length(length);
+  delegate_.SetCanWriteAnything();
+  {
+     InSequence dummy;
+     EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+         SaveArg<0>(&packet_));
+     EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+         SaveArg<0>(&packet2_));
+  }
+  generator_.StartBatchOperations();
+  // Queue enough data to prevent a stream frame with a non-zero offset from
+  // fitting.
+  QuicConsumedData consumed =
+      generator_.ConsumeData(kHeadersStreamId, MakeIOVector("foo"), 0, false,
+                             MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(3u, consumed.bytes_consumed);
+  EXPECT_FALSE(consumed.fin_consumed);
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+
+  // This frame will not fit with the existing frame, causing the queued frame
+  // to be serialized, and it will not fit with another frame like it, so it is
+  // serialized by itself.
+  consumed = generator_.ConsumeData(kHeadersStreamId, MakeIOVector("bar"), 3,
+                                    true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(3u, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+  CheckPacketContains(contents, packet2_);
+}
+
+TEST_F(QuicPacketGeneratorTest, FecGroupSizeOnCongestionWindowChange) {
+  delegate_.SetCanWriteAnything();
+  creator_->set_max_packets_per_fec_group(50);
+  EXPECT_EQ(50u, creator_->max_packets_per_fec_group());
+  EXPECT_FALSE(creator_->IsFecGroupOpen());
+
+  // On reduced cwnd.
+  generator_.OnCongestionWindowChange(7 * kDefaultTCPMSS);
+  EXPECT_EQ(3u, creator_->max_packets_per_fec_group());
+
+  // On increased cwnd.
+  generator_.OnCongestionWindowChange(100 * kDefaultTCPMSS);
+  EXPECT_EQ(50u, creator_->max_packets_per_fec_group());
+
+  // On collapsed cwnd.
+  generator_.OnCongestionWindowChange(1 * kDefaultTCPMSS);
+  EXPECT_EQ(2u, creator_->max_packets_per_fec_group());
+}
+
+TEST_F(QuicPacketGeneratorTest, FecGroupSizeChangeWithOpenGroup) {
+  delegate_.SetCanWriteAnything();
+  // TODO(jri): This starting of batch mode should not be required when
+  // FEC sending is separated from batching operations.
+  generator_.StartBatchOperations();
+  creator_->set_max_packets_per_fec_group(50);
+  EXPECT_EQ(50u, creator_->max_packets_per_fec_group());
+  EXPECT_FALSE(creator_->IsFecGroupOpen());
+
+  // Send enough data to create 4 packets with MUST_FEC_PROTECT flag.
+  // 3 packets are sent, one is queued in the creator.
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+  }
+  size_t data_len = 3 * kDefaultMaxPacketSize + 1;
+  QuicConsumedData consumed = generator_.ConsumeData(
+      7, CreateData(data_len), 0, true, MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  EXPECT_TRUE(creator_->IsFecGroupOpen());
+
+  // Change FEC groupsize.
+  generator_.OnCongestionWindowChange(2 * kDefaultTCPMSS);
+  EXPECT_EQ(2u, creator_->max_packets_per_fec_group());
+
+  // Send enough data to trigger one unprotected data packet,
+  // causing the FEC packet to also be sent.
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet4_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet5_));
+  }
+  consumed = generator_.ConsumeData(7, CreateData(kDefaultMaxPacketSize), 0,
+                                    true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(kDefaultMaxPacketSize, consumed.bytes_consumed);
+  // Verify that one FEC packet was sent.
+  CheckPacketIsFec(packet5_, /*fec_group=*/1u);
+  EXPECT_FALSE(creator_->IsFecGroupOpen());
+  EXPECT_FALSE(creator_->IsFecProtected());
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnOff) {
+  delegate_.SetCanWriteAnything();
+  creator_->set_max_packets_per_fec_group(2);
+  EXPECT_FALSE(creator_->IsFecProtected());
+
+  // Send one unprotected data packet.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet_));
+  QuicConsumedData consumed = generator_.ConsumeData(5, CreateData(1u), 0, true,
+                                                     MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+  EXPECT_FALSE(creator_->IsFecProtected());
+  // Verify that one data packet was sent.
+  PacketContents contents;
+  contents.num_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet4_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet5_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet6_));
+  }
+  // Send enough data to create 3 packets with MUST_FEC_PROTECT flag.
+  size_t data_len = 2 * kDefaultMaxPacketSize + 100;
+  consumed = generator_.ConsumeData(7, CreateData(data_len), 0, true,
+                                    MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  // Verify that two FEC packets were sent.
+  CheckPacketHasSingleStreamFrame(packet2_);
+  CheckPacketHasSingleStreamFrame(packet3_);
+  CheckPacketIsFec(packet4_, /*fec_group=*/2u);
+  CheckPacketHasSingleStreamFrame(packet5_);
+  CheckPacketIsFec(packet6_, /*fec_group=*/5u);  // Sent at the end of stream.
+
+  // Send one unprotected data packet.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet7_));
+  consumed = generator_.ConsumeData(7, CreateData(1u), 0, true, MAY_FEC_PROTECT,
+                                    nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+  EXPECT_FALSE(creator_->IsFecProtected());
+  // Verify that one unprotected data packet was sent.
+  CheckPacketContains(contents, packet7_);
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnWithPendingFrameInCreator) {
+  delegate_.SetCanWriteAnything();
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(2);
+
+  generator_.StartBatchOperations();
+  // Queue enough data to prevent a stream frame with a non-zero offset from
+  // fitting.
+  QuicConsumedData consumed = generator_.ConsumeData(7, CreateData(1u), 0, true,
+                                                     MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  EXPECT_TRUE(creator_->HasPendingFrames());
+
+  // Queue protected data for sending. Should cause queued frames to be flushed.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+      SaveArg<0>(&packet_));
+  EXPECT_FALSE(creator_->IsFecProtected());
+  consumed = generator_.ConsumeData(7, CreateData(1u), 0, true,
+                                    MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  PacketContents contents;
+  contents.num_stream_frames = 1;
+  // Transmitted packet was not FEC protected.
+  CheckPacketContains(contents, packet_);
+  EXPECT_TRUE(creator_->IsFecProtected());
+  EXPECT_TRUE(creator_->HasPendingFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnWithPendingFramesInGenerator) {
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(2);
+
+  // Queue control frames in generator.
+  delegate_.SetCanNotWrite();
+  generator_.SetShouldSendAck(true, true);
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  // Set up frames to write into the creator when control frames are written.
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+      Return(CreateFeedbackFrame()));
+  EXPECT_CALL(delegate_, CreateStopWaitingFrame()).WillOnce(
+      Return(CreateStopWaitingFrame()));
+
+  // Generator should have queued control frames, and creator should be empty.
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+  EXPECT_FALSE(creator_->HasPendingFrames());
+  EXPECT_FALSE(creator_->IsFecProtected());
+
+  // Queue protected data for sending. Should cause queued frames to be flushed.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+      SaveArg<0>(&packet_));
+  QuicConsumedData consumed = generator_.ConsumeData(7, CreateData(1u), 0, true,
+                                                     MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  PacketContents contents;
+  contents.num_ack_frames = 1;
+  contents.num_feedback_frames = 1;
+  contents.num_stop_waiting_frames = 1;
+  CheckPacketContains(contents, packet_);
+
+  // FEC protection should be on in creator.
+  EXPECT_TRUE(creator_->IsFecProtected());
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnOffWithSubsequentFramesProtected) {
+  delegate_.SetCanWriteAnything();
+
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(2);
+  EXPECT_FALSE(creator_->IsFecProtected());
+
+  // Queue stream frame to be protected in creator.
+  generator_.StartBatchOperations();
+  QuicConsumedData consumed = generator_.ConsumeData(5, CreateData(1u), 0, true,
+                                                     MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(1u, consumed.bytes_consumed);
+  // Creator has a pending protected frame.
+  EXPECT_TRUE(creator_->HasPendingFrames());
+  EXPECT_TRUE(creator_->IsFecProtected());
+
+  // Add enough unprotected data to exceed size of current packet, so that
+  // current packet is sent. Both frames will be sent out in a single packet.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  size_t data_len = kDefaultMaxPacketSize;
+  consumed = generator_.ConsumeData(5, CreateData(data_len), 0, true,
+                                    MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  PacketContents contents;
+  contents.num_stream_frames = 2u;
+  contents.fec_group = 1u;
+  CheckPacketContains(contents, packet_);
+  // FEC protection should still be on in creator.
+  EXPECT_TRUE(creator_->IsFecProtected());
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnOffWithSubsequentPacketsProtected) {
+  delegate_.SetCanWriteAnything();
+
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(2);
+  EXPECT_FALSE(creator_->IsFecProtected());
+
+  generator_.StartBatchOperations();
+  // Send first packet, FEC protected.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  // Write enough data to cause a packet to be emitted.
+  size_t data_len = kDefaultMaxPacketSize;
+  QuicConsumedData consumed = generator_.ConsumeData(
+      5, CreateData(data_len), 0, true, MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  PacketContents contents;
+  contents.num_stream_frames = 1u;
+  contents.fec_group = 1u;
+  CheckPacketContains(contents, packet_);
+
+  // FEC should still be on in creator.
+  EXPECT_TRUE(creator_->IsFecProtected());
+
+  // Send enough unprotected data to cause second packet to be sent, which gets
+  // protected because it happens to fall within an open FEC group. Data packet
+  // will be followed by FEC packet.
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+  }
+  consumed = generator_.ConsumeData(5, CreateData(data_len), 0, true,
+                                    MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  contents.num_stream_frames = 2u;
+  CheckPacketContains(contents, packet2_);
+  CheckPacketIsFec(packet3_, /*fec_group=*/1u);
+
+  // FEC protection should be off in creator.
+  EXPECT_FALSE(creator_->IsFecProtected());
+}
+
+TEST_F(QuicPacketGeneratorTest, SwitchFecOnOffThenOnWithCreatorProtectionOn) {
+  delegate_.SetCanWriteAnything();
+  generator_.StartBatchOperations();
+
+  // Enable FEC.
+  creator_->set_max_packets_per_fec_group(2);
+  EXPECT_FALSE(creator_->IsFecProtected());
+
+  // Queue one byte of FEC protected data.
+  QuicConsumedData consumed = generator_.ConsumeData(5, CreateData(1u), 0, true,
+                                                     MUST_FEC_PROTECT, nullptr);
+  EXPECT_TRUE(creator_->HasPendingFrames());
+
+  // Add more unprotected data causing first packet to be sent, FEC protected.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+      SaveArg<0>(&packet_));
+  size_t data_len = kDefaultMaxPacketSize;
+  consumed = generator_.ConsumeData(5, CreateData(data_len), 0, true,
+                                    MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  PacketContents contents;
+  contents.num_stream_frames = 2u;
+  contents.fec_group = 1u;
+  CheckPacketContains(contents, packet_);
+
+  // FEC group is still open in creator.
+  EXPECT_TRUE(creator_->IsFecProtected());
+
+  // Add data that should be protected, large enough to cause second packet to
+  // be sent. Data packet should be followed by FEC packet.
+  {
+    InSequence dummy;
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet3_));
+  }
+  consumed = generator_.ConsumeData(5, CreateData(data_len), 0, true,
+                                    MUST_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  CheckPacketContains(contents, packet2_);
+  CheckPacketIsFec(packet3_, /*fec_group=*/1u);
+
+  // FEC protection should remain on in creator.
+  EXPECT_TRUE(creator_->IsFecProtected());
+}
+
+TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations) {
+  delegate_.SetCanNotWrite();
+
+  generator_.SetShouldSendAck(true, false);
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+
+  delegate_.SetCanWriteAnything();
+
+  generator_.StartBatchOperations();
+
+  // When the first write operation is invoked, the ack and feedback
+  // frames will be returned.
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+      Return(CreateFeedbackFrame()));
+
+  // Send some data and a control frame
+  generator_.ConsumeData(3, MakeIOVector("quux"), 7, false, MAY_FEC_PROTECT,
+                         nullptr);
+  generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame()));
+
+  // All five frames will be flushed out in a single packet.
+  EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(SaveArg<0>(&packet_));
+  generator_.FinishBatchOperations();
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  PacketContents contents;
+  contents.num_ack_frames = 1;
+  contents.num_goaway_frames = 1;
+  contents.num_feedback_frames = 1;
+  contents.num_rst_stream_frames = 1;
+  contents.num_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations2) {
+  delegate_.SetCanNotWrite();
+
+  generator_.SetShouldSendAck(true, false);
+  generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+  EXPECT_TRUE(generator_.HasQueuedFrames());
+
+  delegate_.SetCanWriteAnything();
+
+  generator_.StartBatchOperations();
+
+  // When the first write operation is invoked, the ack and feedback
+  // frames will be returned.
+  EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+  EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+      Return(CreateFeedbackFrame()));
+
+  {
+    InSequence dummy;
+    // All five frames will be flushed out in a single packet
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet_));
+    EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+        SaveArg<0>(&packet2_));
+  }
+
+  // Send enough data to exceed one packet
+  size_t data_len = kDefaultMaxPacketSize + 100;
+  QuicConsumedData consumed = generator_.ConsumeData(
+      3, CreateData(data_len), 0, true, MAY_FEC_PROTECT, nullptr);
+  EXPECT_EQ(data_len, consumed.bytes_consumed);
+  EXPECT_TRUE(consumed.fin_consumed);
+  generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame()));
+
+  generator_.FinishBatchOperations();
+  EXPECT_FALSE(generator_.HasQueuedFrames());
+
+  // The first packet should have the queued data and part of the stream data.
+  PacketContents contents;
+  contents.num_ack_frames = 1;
+  contents.num_feedback_frames = 1;
+  contents.num_rst_stream_frames = 1;
+  contents.num_stream_frames = 1;
+  CheckPacketContains(contents, packet_);
+
+  // The second should have the remainder of the stream data.
+  PacketContents contents2;
+  contents2.num_goaway_frames = 1;
+  contents2.num_stream_frames = 1;
+  CheckPacketContains(contents2, packet2_);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_packet_writer.h b/net/quic/quic_packet_writer.h
new file mode 100644
index 0000000..16b7ade
--- /dev/null
+++ b/net/quic/quic_packet_writer.h
@@ -0,0 +1,46 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_PACKET_WRITER_H_
+#define NET_QUIC_QUIC_PACKET_WRITER_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+struct WriteResult;
+
+// An interface between writers and the entity managing the
+// socket (in our case the QuicDispatcher).  This allows the Dispatcher to
+// control writes, and manage any writers who end up write blocked.
+class NET_EXPORT_PRIVATE QuicPacketWriter {
+ public:
+  virtual ~QuicPacketWriter() {}
+
+  // Sends the packet out to the peer.  If the write succeeded, the result's
+  // status is WRITE_STATUS_OK and bytes_written is populated. If the write
+  // failed, the result's status is WRITE_STATUS_BLOCKED or WRITE_STATUS_ERROR
+  // and error_code is populated.
+  virtual WriteResult WritePacket(
+      const char* buffer, size_t buf_len,
+      const IPAddressNumber& self_address,
+      const IPEndPoint& peer_address) = 0;
+
+  // Returns true if the writer buffers and subsequently rewrites data
+  // when an attempt to write results in the underlying socket becoming
+  // write blocked.
+  virtual bool IsWriteBlockedDataBuffered() const = 0;
+
+  // Returns true if the network socket is not writable.
+  virtual bool IsWriteBlocked() const = 0;
+
+  // Records that the socket has become writable, for example when an EPOLLOUT
+  // is received or an asynchronous write completes.
+  virtual void SetWritable() = 0;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_PACKET_WRITER_H_
diff --git a/net/quic/quic_per_connection_packet_writer.cc b/net/quic/quic_per_connection_packet_writer.cc
new file mode 100644
index 0000000..3882da8
--- /dev/null
+++ b/net/quic/quic_per_connection_packet_writer.cc
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_per_connection_packet_writer.h"
+
+#include "net/quic/quic_server_packet_writer.h"
+
+namespace net {
+
+QuicPerConnectionPacketWriter::QuicPerConnectionPacketWriter(
+    QuicServerPacketWriter* shared_writer,
+    QuicConnection* connection)
+    : shared_writer_(shared_writer),
+      connection_(connection),
+      weak_factory_(this){
+}
+
+QuicPerConnectionPacketWriter::~QuicPerConnectionPacketWriter() {
+}
+
+QuicPacketWriter* QuicPerConnectionPacketWriter::shared_writer() const {
+  return shared_writer_;
+}
+
+WriteResult QuicPerConnectionPacketWriter::WritePacket(
+    const char* buffer,
+    size_t buf_len,
+    const IPAddressNumber& self_address,
+    const IPEndPoint& peer_address) {
+  return shared_writer_->WritePacketWithCallback(
+      buffer,
+      buf_len,
+      self_address,
+      peer_address,
+      base::Bind(&QuicPerConnectionPacketWriter::OnWriteComplete,
+                 weak_factory_.GetWeakPtr()));
+}
+
+bool QuicPerConnectionPacketWriter::IsWriteBlockedDataBuffered() const {
+  return shared_writer_->IsWriteBlockedDataBuffered();
+}
+
+bool QuicPerConnectionPacketWriter::IsWriteBlocked() const {
+  return shared_writer_->IsWriteBlocked();
+}
+
+void QuicPerConnectionPacketWriter::SetWritable() {
+  shared_writer_->SetWritable();
+}
+
+void QuicPerConnectionPacketWriter::OnWriteComplete(WriteResult result) {
+  if (result.status == WRITE_STATUS_ERROR) {
+    connection_->OnWriteError(result.error_code);
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_per_connection_packet_writer.h b/net/quic/quic_per_connection_packet_writer.h
new file mode 100644
index 0000000..9b9b912
--- /dev/null
+++ b/net/quic/quic_per_connection_packet_writer.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_PER_CONNECTION_PACKET_WRITER_H_
+#define NET_QUIC_QUIC_PER_CONNECTION_PACKET_WRITER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_packet_writer.h"
+
+namespace net {
+
+class QuicServerPacketWriter;
+
+// A connection-specific packet writer that notifies its connection when its
+// writes to the shared QuicServerPacketWriter complete.
+// This class is necessary because multiple connections can share the same
+// QuicServerPacketWriter, so it has no way to know which connection to notify.
+class QuicPerConnectionPacketWriter : public QuicPacketWriter {
+ public:
+  // Does not take ownership of |shared_writer| or |connection|.
+  QuicPerConnectionPacketWriter(QuicServerPacketWriter* shared_writer,
+                                QuicConnection* connection);
+  virtual ~QuicPerConnectionPacketWriter();
+
+  QuicPacketWriter* shared_writer() const;
+  QuicConnection* connection() const { return connection_; }
+
+  // Default implementation of the QuicPacketWriter interface: Passes everything
+  // to |shared_writer_|.
+  virtual WriteResult WritePacket(const char* buffer,
+                                  size_t buf_len,
+                                  const IPAddressNumber& self_address,
+                                  const IPEndPoint& peer_address) OVERRIDE;
+  virtual bool IsWriteBlockedDataBuffered() const OVERRIDE;
+  virtual bool IsWriteBlocked() const OVERRIDE;
+  virtual void SetWritable() OVERRIDE;
+
+ private:
+  void OnWriteComplete(WriteResult result);
+
+  QuicServerPacketWriter* shared_writer_;  // Not owned.
+  QuicConnection* connection_;  // Not owned.
+
+  base::WeakPtrFactory<QuicPerConnectionPacketWriter> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicPerConnectionPacketWriter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_PER_CONNECTION_PACKET_WRITER_H_
diff --git a/net/quic/quic_protocol.cc b/net/quic/quic_protocol.cc
new file mode 100644
index 0000000..96a4557
--- /dev/null
+++ b/net/quic/quic_protocol.cc
@@ -0,0 +1,749 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_protocol.h"
+
+#include "base/stl_util.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::map;
+using std::numeric_limits;
+using std::ostream;
+using std::string;
+
+namespace net {
+
+size_t GetPacketHeaderSize(const QuicPacketHeader& header) {
+  return GetPacketHeaderSize(header.public_header.connection_id_length,
+                             header.public_header.version_flag,
+                             header.public_header.sequence_number_length,
+                             header.is_in_fec_group);
+}
+
+size_t GetPacketHeaderSize(QuicConnectionIdLength connection_id_length,
+                           bool include_version,
+                           QuicSequenceNumberLength sequence_number_length,
+                           InFecGroup is_in_fec_group) {
+  return kPublicFlagsSize + connection_id_length +
+      (include_version ? kQuicVersionSize : 0) + sequence_number_length +
+      kPrivateFlagsSize + (is_in_fec_group == IN_FEC_GROUP ? kFecGroupSize : 0);
+}
+
+size_t GetStartOfFecProtectedData(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length) {
+  return GetPacketHeaderSize(connection_id_length,
+                             include_version,
+                             sequence_number_length,
+                             IN_FEC_GROUP);
+}
+
+size_t GetStartOfEncryptedData(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length) {
+  // Don't include the fec size, since encryption starts before private flags.
+  return GetPacketHeaderSize(connection_id_length,
+                             include_version,
+                             sequence_number_length,
+                             NOT_IN_FEC_GROUP) - kPrivateFlagsSize;
+}
+
+QuicPacketPublicHeader::QuicPacketPublicHeader()
+    : connection_id(0),
+      connection_id_length(PACKET_8BYTE_CONNECTION_ID),
+      reset_flag(false),
+      version_flag(false),
+      sequence_number_length(PACKET_6BYTE_SEQUENCE_NUMBER) {
+}
+
+QuicPacketPublicHeader::QuicPacketPublicHeader(
+    const QuicPacketPublicHeader& other)
+    : connection_id(other.connection_id),
+      connection_id_length(other.connection_id_length),
+      reset_flag(other.reset_flag),
+      version_flag(other.version_flag),
+      sequence_number_length(other.sequence_number_length),
+      versions(other.versions) {
+}
+
+QuicPacketPublicHeader::~QuicPacketPublicHeader() {}
+
+QuicPacketHeader::QuicPacketHeader()
+    : fec_flag(false),
+      entropy_flag(false),
+      entropy_hash(0),
+      packet_sequence_number(0),
+      is_in_fec_group(NOT_IN_FEC_GROUP),
+      fec_group(0) {
+}
+
+QuicPacketHeader::QuicPacketHeader(const QuicPacketPublicHeader& header)
+    : public_header(header),
+      fec_flag(false),
+      entropy_flag(false),
+      entropy_hash(0),
+      packet_sequence_number(0),
+      is_in_fec_group(NOT_IN_FEC_GROUP),
+      fec_group(0) {
+}
+
+QuicPublicResetPacket::QuicPublicResetPacket()
+    : nonce_proof(0),
+      rejected_sequence_number(0) {}
+
+QuicPublicResetPacket::QuicPublicResetPacket(
+    const QuicPacketPublicHeader& header)
+    : public_header(header),
+      nonce_proof(0),
+      rejected_sequence_number(0) {}
+
+QuicStreamFrame::QuicStreamFrame()
+    : stream_id(0),
+      fin(false),
+      offset(0),
+      notifier(nullptr) {}
+
+QuicStreamFrame::QuicStreamFrame(const QuicStreamFrame& frame)
+    : stream_id(frame.stream_id),
+      fin(frame.fin),
+      offset(frame.offset),
+      data(frame.data),
+      notifier(frame.notifier) {
+}
+
+QuicStreamFrame::QuicStreamFrame(QuicStreamId stream_id,
+                                 bool fin,
+                                 QuicStreamOffset offset,
+                                 IOVector data)
+    : stream_id(stream_id),
+      fin(fin),
+      offset(offset),
+      data(data),
+      notifier(nullptr) {}
+
+string* QuicStreamFrame::GetDataAsString() const {
+  string* data_string = new string();
+  data_string->reserve(data.TotalBufferSize());
+  for (size_t i = 0; i < data.Size(); ++i) {
+    data_string->append(static_cast<char*>(data.iovec()[i].iov_base),
+                        data.iovec()[i].iov_len);
+  }
+  DCHECK_EQ(data_string->size(), data.TotalBufferSize());
+  return data_string;
+}
+
+uint32 MakeQuicTag(char a, char b, char c, char d) {
+  return static_cast<uint32>(a) |
+         static_cast<uint32>(b) << 8 |
+         static_cast<uint32>(c) << 16 |
+         static_cast<uint32>(d) << 24;
+}
+
+bool ContainsQuicTag(const QuicTagVector& tag_vector, QuicTag tag) {
+  return std::find(tag_vector.begin(), tag_vector.end(),  tag)
+      != tag_vector.end();
+}
+
+QuicVersionVector QuicSupportedVersions() {
+  QuicVersionVector supported_versions;
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    supported_versions.push_back(kSupportedQuicVersions[i]);
+  }
+  return supported_versions;
+}
+
+QuicTag QuicVersionToQuicTag(const QuicVersion version) {
+  switch (version) {
+    case QUIC_VERSION_18:
+      return MakeQuicTag('Q', '0', '1', '8');
+    case QUIC_VERSION_19:
+      return MakeQuicTag('Q', '0', '1', '9');
+    case QUIC_VERSION_21:
+      return MakeQuicTag('Q', '0', '2', '1');
+    case QUIC_VERSION_22:
+      return MakeQuicTag('Q', '0', '2', '2');
+    case QUIC_VERSION_23:
+      return MakeQuicTag('Q', '0', '2', '3');
+    default:
+      // This shold be an ERROR because we should never attempt to convert an
+      // invalid QuicVersion to be written to the wire.
+      LOG(ERROR) << "Unsupported QuicVersion: " << version;
+      return 0;
+  }
+}
+
+QuicVersion QuicTagToQuicVersion(const QuicTag version_tag) {
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    if (version_tag == QuicVersionToQuicTag(kSupportedQuicVersions[i])) {
+      return kSupportedQuicVersions[i];
+    }
+  }
+  // Reading from the client so this should not be considered an ERROR.
+  DVLOG(1) << "Unsupported QuicTag version: "
+           << QuicUtils::TagToString(version_tag);
+  return QUIC_VERSION_UNSUPPORTED;
+}
+
+#define RETURN_STRING_LITERAL(x) \
+case x: \
+return #x
+
+string QuicVersionToString(const QuicVersion version) {
+  switch (version) {
+    RETURN_STRING_LITERAL(QUIC_VERSION_18);
+    RETURN_STRING_LITERAL(QUIC_VERSION_19);
+    RETURN_STRING_LITERAL(QUIC_VERSION_21);
+    RETURN_STRING_LITERAL(QUIC_VERSION_22);
+    RETURN_STRING_LITERAL(QUIC_VERSION_23);
+    default:
+      return "QUIC_VERSION_UNSUPPORTED";
+  }
+}
+
+string QuicVersionVectorToString(const QuicVersionVector& versions) {
+  string result = "";
+  for (size_t i = 0; i < versions.size(); ++i) {
+    if (i != 0) {
+      result.append(",");
+    }
+    result.append(QuicVersionToString(versions[i]));
+  }
+  return result;
+}
+
+ostream& operator<<(ostream& os, const QuicPacketHeader& header) {
+  os << "{ connection_id: " << header.public_header.connection_id
+     << ", connection_id_length:" << header.public_header.connection_id_length
+     << ", sequence_number_length:"
+     << header.public_header.sequence_number_length
+     << ", reset_flag: " << header.public_header.reset_flag
+     << ", version_flag: " << header.public_header.version_flag;
+  if (header.public_header.version_flag) {
+    os << " version: ";
+    for (size_t i = 0; i < header.public_header.versions.size(); ++i) {
+      os << header.public_header.versions[0] << " ";
+    }
+  }
+  os << ", fec_flag: " << header.fec_flag
+     << ", entropy_flag: " << header.entropy_flag
+     << ", entropy hash: " << static_cast<int>(header.entropy_hash)
+     << ", sequence_number: " << header.packet_sequence_number
+     << ", is_in_fec_group:" << header.is_in_fec_group
+     << ", fec_group: " << header.fec_group<< "}\n";
+  return os;
+}
+
+bool IsAwaitingPacket(const QuicAckFrame& ack_frame,
+                      QuicPacketSequenceNumber sequence_number) {
+  return sequence_number > ack_frame.largest_observed ||
+      ContainsKey(ack_frame.missing_packets, sequence_number);
+}
+
+void InsertMissingPacketsBetween(QuicAckFrame* ack_frame,
+                                 QuicPacketSequenceNumber lower,
+                                 QuicPacketSequenceNumber higher) {
+  for (QuicPacketSequenceNumber i = lower; i < higher; ++i) {
+    ack_frame->missing_packets.insert(i);
+  }
+}
+
+QuicStopWaitingFrame::QuicStopWaitingFrame()
+    : entropy_hash(0),
+      least_unacked(0) {
+}
+
+QuicStopWaitingFrame::~QuicStopWaitingFrame() {}
+
+QuicAckFrame::QuicAckFrame()
+    : entropy_hash(0),
+      largest_observed(0),
+      delta_time_largest_observed(QuicTime::Delta::Infinite()),
+      is_truncated(false) {}
+
+QuicAckFrame::~QuicAckFrame() {}
+
+CongestionFeedbackMessageTCP::CongestionFeedbackMessageTCP()
+    : receive_window(0) {
+}
+
+QuicCongestionFeedbackFrame::QuicCongestionFeedbackFrame() : type(kTCP) {}
+
+QuicCongestionFeedbackFrame::~QuicCongestionFeedbackFrame() {}
+
+QuicRstStreamErrorCode AdjustErrorForVersion(
+    QuicRstStreamErrorCode error_code,
+    QuicVersion version) {
+  switch (error_code) {
+    case QUIC_RST_FLOW_CONTROL_ACCOUNTING:
+      if (version < QUIC_VERSION_18) {
+        return QUIC_STREAM_NO_ERROR;
+      }
+      break;
+    default:
+      return error_code;
+  }
+  return error_code;
+}
+
+QuicRstStreamFrame::QuicRstStreamFrame()
+    : stream_id(0),
+      error_code(QUIC_STREAM_NO_ERROR) {
+}
+
+QuicRstStreamFrame::QuicRstStreamFrame(QuicStreamId stream_id,
+                                       QuicRstStreamErrorCode error_code,
+                                       QuicStreamOffset bytes_written)
+    : stream_id(stream_id),
+      error_code(error_code),
+      byte_offset(bytes_written) {
+  DCHECK_LE(error_code, numeric_limits<uint8>::max());
+}
+
+QuicConnectionCloseFrame::QuicConnectionCloseFrame()
+    : error_code(QUIC_NO_ERROR) {
+}
+
+QuicFrame::QuicFrame() {}
+
+QuicFrame::QuicFrame(QuicPaddingFrame* padding_frame)
+    : type(PADDING_FRAME),
+      padding_frame(padding_frame) {
+}
+
+QuicFrame::QuicFrame(QuicStreamFrame* stream_frame)
+    : type(STREAM_FRAME),
+      stream_frame(stream_frame) {
+}
+
+QuicFrame::QuicFrame(QuicAckFrame* frame)
+    : type(ACK_FRAME),
+      ack_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicCongestionFeedbackFrame* frame)
+    : type(CONGESTION_FEEDBACK_FRAME),
+      congestion_feedback_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicStopWaitingFrame* frame)
+    : type(STOP_WAITING_FRAME),
+      stop_waiting_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicPingFrame* frame)
+    : type(PING_FRAME),
+      ping_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicRstStreamFrame* frame)
+    : type(RST_STREAM_FRAME),
+      rst_stream_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicConnectionCloseFrame* frame)
+    : type(CONNECTION_CLOSE_FRAME),
+      connection_close_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicGoAwayFrame* frame)
+    : type(GOAWAY_FRAME),
+      goaway_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicWindowUpdateFrame* frame)
+    : type(WINDOW_UPDATE_FRAME),
+      window_update_frame(frame) {
+}
+
+QuicFrame::QuicFrame(QuicBlockedFrame* frame)
+    : type(BLOCKED_FRAME),
+      blocked_frame(frame) {
+}
+
+QuicFecData::QuicFecData() : fec_group(0) {}
+
+ostream& operator<<(ostream& os, const QuicStopWaitingFrame& sent_info) {
+  os << "entropy_hash: " << static_cast<int>(sent_info.entropy_hash)
+     << " least_unacked: " << sent_info.least_unacked;
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicAckFrame& ack_frame) {
+  os << "entropy_hash: " << static_cast<int>(ack_frame.entropy_hash)
+     << " largest_observed: " << ack_frame.largest_observed
+     << " delta_time_largest_observed: "
+     << ack_frame.delta_time_largest_observed.ToMicroseconds()
+     << " missing_packets: [ ";
+  for (SequenceNumberSet::const_iterator it = ack_frame.missing_packets.begin();
+       it != ack_frame.missing_packets.end(); ++it) {
+    os << *it << " ";
+  }
+  os << " ] is_truncated: " << ack_frame.is_truncated;
+  os << " revived_packets: [ ";
+  for (SequenceNumberSet::const_iterator it = ack_frame.revived_packets.begin();
+       it != ack_frame.revived_packets.end(); ++it) {
+    os << *it << " ";
+  }
+  os << " ] received_packets: [ ";
+  for (PacketTimeList::const_iterator it =
+           ack_frame.received_packet_times.begin();
+           it != ack_frame.received_packet_times.end(); ++it) {
+    os << it->first << " at " << it->second.ToDebuggingValue() << " ";
+  }
+  os << " ]";
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicFrame& frame) {
+  switch (frame.type) {
+  case PADDING_FRAME: {
+      os << "type { PADDING_FRAME } ";
+      break;
+    }
+    case RST_STREAM_FRAME: {
+      os << "type { " << RST_STREAM_FRAME << " } " << *(frame.rst_stream_frame);
+      break;
+    }
+    case CONNECTION_CLOSE_FRAME: {
+      os << "type { CONNECTION_CLOSE_FRAME } "
+         << *(frame.connection_close_frame);
+      break;
+    }
+    case GOAWAY_FRAME: {
+      os << "type { GOAWAY_FRAME } " << *(frame.goaway_frame);
+      break;
+    }
+    case WINDOW_UPDATE_FRAME: {
+      os << "type { WINDOW_UPDATE_FRAME } " << *(frame.window_update_frame);
+      break;
+    }
+    case BLOCKED_FRAME: {
+      os << "type { BLOCKED_FRAME } " << *(frame.blocked_frame);
+      break;
+    }
+    case STREAM_FRAME: {
+      os << "type { STREAM_FRAME } " << *(frame.stream_frame);
+      break;
+    }
+    case ACK_FRAME: {
+      os << "type { ACK_FRAME } " << *(frame.ack_frame);
+      break;
+    }
+    case CONGESTION_FEEDBACK_FRAME: {
+      os << "type { CONGESTION_FEEDBACK_FRAME } "
+         << *(frame.congestion_feedback_frame);
+      break;
+    }
+    case STOP_WAITING_FRAME: {
+      os << "type { STOP_WAITING_FRAME } " << *(frame.stop_waiting_frame);
+      break;
+    }
+    case PING_FRAME: {
+      os << "type { PING_FRAME } ";
+      break;
+    }
+    default: {
+      LOG(ERROR) << "Unknown frame type: " << frame.type;
+      break;
+    }
+  }
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicRstStreamFrame& rst_frame) {
+  os << "stream_id { " << rst_frame.stream_id << " } "
+     << "error_code { " << rst_frame.error_code << " } "
+     << "error_details { " << rst_frame.error_details << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os,
+                    const QuicConnectionCloseFrame& connection_close_frame) {
+  os << "error_code { " << connection_close_frame.error_code << " } "
+     << "error_details { " << connection_close_frame.error_details << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicGoAwayFrame& goaway_frame) {
+  os << "error_code { " << goaway_frame.error_code << " } "
+     << "last_good_stream_id { " << goaway_frame.last_good_stream_id << " } "
+     << "reason_phrase { " << goaway_frame.reason_phrase << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os,
+                    const QuicWindowUpdateFrame& window_update_frame) {
+  os << "stream_id { " << window_update_frame.stream_id << " } "
+     << "byte_offset { " << window_update_frame.byte_offset << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicBlockedFrame& blocked_frame) {
+  os << "stream_id { " << blocked_frame.stream_id << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os, const QuicStreamFrame& stream_frame) {
+  os << "stream_id { " << stream_frame.stream_id << " } "
+     << "fin { " << stream_frame.fin << " } "
+     << "offset { " << stream_frame.offset << " } "
+     << "data { "
+     << QuicUtils::StringToHexASCIIDump(*(stream_frame.GetDataAsString()))
+     << " }\n";
+  return os;
+}
+
+ostream& operator<<(ostream& os,
+                    const QuicCongestionFeedbackFrame& congestion_frame) {
+  os << "type: " << congestion_frame.type;
+  switch (congestion_frame.type) {
+    case kTCP: {
+      const CongestionFeedbackMessageTCP& tcp = congestion_frame.tcp;
+      os << " receive_window: " << tcp.receive_window;
+      break;
+    }
+  }
+  return os;
+}
+
+QuicGoAwayFrame::QuicGoAwayFrame()
+    : error_code(QUIC_NO_ERROR),
+      last_good_stream_id(0) {
+}
+
+QuicGoAwayFrame::QuicGoAwayFrame(QuicErrorCode error_code,
+                                 QuicStreamId last_good_stream_id,
+                                 const string& reason)
+    : error_code(error_code),
+      last_good_stream_id(last_good_stream_id),
+      reason_phrase(reason) {
+  DCHECK_LE(error_code, numeric_limits<uint8>::max());
+}
+
+QuicData::QuicData(const char* buffer,
+                   size_t length)
+    : buffer_(buffer),
+      length_(length),
+      owns_buffer_(false) {
+}
+
+QuicData::QuicData(char* buffer,
+                   size_t length,
+                   bool owns_buffer)
+    : buffer_(buffer),
+      length_(length),
+      owns_buffer_(owns_buffer) {
+}
+
+QuicData::~QuicData() {
+  if (owns_buffer_) {
+    delete [] const_cast<char*>(buffer_);
+  }
+}
+
+QuicWindowUpdateFrame::QuicWindowUpdateFrame(QuicStreamId stream_id,
+                                             QuicStreamOffset byte_offset)
+    : stream_id(stream_id),
+      byte_offset(byte_offset) {}
+
+QuicBlockedFrame::QuicBlockedFrame(QuicStreamId stream_id)
+    : stream_id(stream_id) {}
+
+QuicPacket::QuicPacket(char* buffer,
+                       size_t length,
+                       bool owns_buffer,
+                       QuicConnectionIdLength connection_id_length,
+                       bool includes_version,
+                       QuicSequenceNumberLength sequence_number_length,
+                       bool is_fec_packet)
+    : QuicData(buffer, length, owns_buffer),
+      buffer_(buffer),
+      is_fec_packet_(is_fec_packet),
+      connection_id_length_(connection_id_length),
+      includes_version_(includes_version),
+      sequence_number_length_(sequence_number_length) {
+}
+
+QuicEncryptedPacket::QuicEncryptedPacket(const char* buffer,
+                                         size_t length)
+    : QuicData(buffer, length) {
+}
+
+QuicEncryptedPacket::QuicEncryptedPacket(char* buffer,
+                                         size_t length,
+                                         bool owns_buffer)
+      : QuicData(buffer, length, owns_buffer) {
+}
+
+StringPiece QuicPacket::FecProtectedData() const {
+  const size_t start_of_fec = GetStartOfFecProtectedData(
+      connection_id_length_, includes_version_, sequence_number_length_);
+  return StringPiece(data() + start_of_fec, length() - start_of_fec);
+}
+
+StringPiece QuicPacket::AssociatedData() const {
+  return StringPiece(
+      data() + kStartOfHashData,
+      GetStartOfEncryptedData(
+          connection_id_length_, includes_version_, sequence_number_length_) -
+      kStartOfHashData);
+}
+
+StringPiece QuicPacket::BeforePlaintext() const {
+  return StringPiece(data(), GetStartOfEncryptedData(connection_id_length_,
+                                                     includes_version_,
+                                                     sequence_number_length_));
+}
+
+StringPiece QuicPacket::Plaintext() const {
+  const size_t start_of_encrypted_data =
+      GetStartOfEncryptedData(
+          connection_id_length_, includes_version_, sequence_number_length_);
+  return StringPiece(data() + start_of_encrypted_data,
+                     length() - start_of_encrypted_data);
+}
+
+RetransmittableFrames::RetransmittableFrames()
+    : encryption_level_(NUM_ENCRYPTION_LEVELS),
+      has_crypto_handshake_(NOT_HANDSHAKE) {
+}
+
+RetransmittableFrames::~RetransmittableFrames() {
+  for (QuicFrames::iterator it = frames_.begin(); it != frames_.end(); ++it) {
+    switch (it->type) {
+      case PADDING_FRAME:
+        delete it->padding_frame;
+        break;
+      case STREAM_FRAME:
+        delete it->stream_frame;
+        break;
+      case ACK_FRAME:
+        delete it->ack_frame;
+        break;
+      case CONGESTION_FEEDBACK_FRAME:
+        delete it->congestion_feedback_frame;
+        break;
+      case STOP_WAITING_FRAME:
+        delete it->stop_waiting_frame;
+        break;
+      case PING_FRAME:
+        delete it->ping_frame;
+        break;
+      case RST_STREAM_FRAME:
+        delete it->rst_stream_frame;
+        break;
+      case CONNECTION_CLOSE_FRAME:
+        delete it->connection_close_frame;
+        break;
+      case GOAWAY_FRAME:
+        delete it->goaway_frame;
+        break;
+      case WINDOW_UPDATE_FRAME:
+        delete it->window_update_frame;
+        break;
+      case BLOCKED_FRAME:
+        delete it->blocked_frame;
+        break;
+      case NUM_FRAME_TYPES:
+        DCHECK(false) << "Cannot delete type: " << it->type;
+    }
+  }
+  STLDeleteElements(&stream_data_);
+}
+
+const QuicFrame& RetransmittableFrames::AddStreamFrame(
+    QuicStreamFrame* stream_frame) {
+  // Make an owned copy of the stream frame's data.
+  stream_data_.push_back(stream_frame->GetDataAsString());
+  // Ensure the stream frame's IOVector points to the owned copy of the data.
+  stream_frame->data.Clear();
+  stream_frame->data.Append(const_cast<char*>(stream_data_.back()->data()),
+                            stream_data_.back()->size());
+  frames_.push_back(QuicFrame(stream_frame));
+  if (stream_frame->stream_id == kCryptoStreamId) {
+    has_crypto_handshake_ = IS_HANDSHAKE;
+  }
+  return frames_.back();
+}
+
+const QuicFrame& RetransmittableFrames::AddNonStreamFrame(
+    const QuicFrame& frame) {
+  DCHECK_NE(frame.type, STREAM_FRAME);
+  frames_.push_back(frame);
+  return frames_.back();
+}
+
+void RetransmittableFrames::set_encryption_level(EncryptionLevel level) {
+  encryption_level_ = level;
+}
+
+SerializedPacket::SerializedPacket(
+    QuicPacketSequenceNumber sequence_number,
+    QuicSequenceNumberLength sequence_number_length,
+    QuicPacket* packet,
+    QuicPacketEntropyHash entropy_hash,
+    RetransmittableFrames* retransmittable_frames)
+    : sequence_number(sequence_number),
+      sequence_number_length(sequence_number_length),
+      packet(packet),
+      entropy_hash(entropy_hash),
+      retransmittable_frames(retransmittable_frames) {
+}
+
+SerializedPacket::~SerializedPacket() {}
+
+QuicEncryptedPacket* QuicEncryptedPacket::Clone() const {
+  char* buffer = new char[this->length()];
+  memcpy(buffer, this->data(), this->length());
+  return new QuicEncryptedPacket(buffer, this->length(), true);
+}
+
+ostream& operator<<(ostream& os, const QuicEncryptedPacket& s) {
+  os << s.length() << "-byte data";
+  return os;
+}
+
+TransmissionInfo::TransmissionInfo()
+    : retransmittable_frames(nullptr),
+      sequence_number_length(PACKET_1BYTE_SEQUENCE_NUMBER),
+      sent_time(QuicTime::Zero()),
+      bytes_sent(0),
+      nack_count(0),
+      transmission_type(NOT_RETRANSMISSION),
+      all_transmissions(nullptr),
+      in_flight(false),
+      is_unackable(false) {}
+
+TransmissionInfo::TransmissionInfo(
+    RetransmittableFrames* retransmittable_frames,
+    QuicSequenceNumberLength sequence_number_length)
+    : retransmittable_frames(retransmittable_frames),
+      sequence_number_length(sequence_number_length),
+      sent_time(QuicTime::Zero()),
+      bytes_sent(0),
+      nack_count(0),
+      transmission_type(NOT_RETRANSMISSION),
+      all_transmissions(nullptr),
+      in_flight(false),
+      is_unackable(false) {}
+
+TransmissionInfo::TransmissionInfo(
+    RetransmittableFrames* retransmittable_frames,
+    QuicSequenceNumberLength sequence_number_length,
+    TransmissionType transmission_type,
+    SequenceNumberList* all_transmissions)
+    : retransmittable_frames(retransmittable_frames),
+      sequence_number_length(sequence_number_length),
+      sent_time(QuicTime::Zero()),
+      bytes_sent(0),
+      nack_count(0),
+      transmission_type(transmission_type),
+      all_transmissions(all_transmissions),
+      in_flight(false),
+      is_unackable(false) {}
+
+}  // namespace net
diff --git a/net/quic/quic_protocol.h b/net/quic/quic_protocol.h
new file mode 100644
index 0000000..6a1bec5
--- /dev/null
+++ b/net/quic/quic_protocol.h
@@ -0,0 +1,1093 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_PROTOCOL_H_
+#define NET_QUIC_QUIC_PROTOCOL_H_
+
+#include <stddef.h>
+#include <limits>
+#include <list>
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+#include "net/quic/iovector.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class QuicAckNotifier;
+class QuicPacket;
+struct QuicPacketHeader;
+
+typedef uint64 QuicConnectionId;
+typedef uint32 QuicStreamId;
+typedef uint64 QuicStreamOffset;
+typedef uint64 QuicPacketSequenceNumber;
+typedef QuicPacketSequenceNumber QuicFecGroupNumber;
+typedef uint64 QuicPublicResetNonceProof;
+typedef uint8 QuicPacketEntropyHash;
+typedef uint32 QuicHeaderId;
+// QuicTag is the type of a tag in the wire protocol.
+typedef uint32 QuicTag;
+typedef std::vector<QuicTag> QuicTagVector;
+typedef std::map<QuicTag, std::string> QuicTagValueMap;
+// TODO(rtenneti): Didn't use SpdyPriority because SpdyPriority is uint8 and
+// QuicPriority is uint32. Use SpdyPriority when we change the QUIC_VERSION.
+typedef uint32 QuicPriority;
+
+// TODO(rch): Consider Quic specific names for these constants.
+// Default and initial maximum size in bytes of a QUIC packet.
+const QuicByteCount kDefaultMaxPacketSize = 1350;
+// The maximum packet size of any QUIC packet, based on ethernet's max size,
+// minus the IP and UDP headers. IPv6 has a 40 byte header, UPD adds an
+// additional 8 bytes.  This is a total overhead of 48 bytes.  Ethernet's
+// max packet size is 1500 bytes,  1500 - 48 = 1452.
+const QuicByteCount kMaxPacketSize = 1452;
+// Default maximum packet size used in Linux TCP implementations.
+const QuicByteCount kDefaultTCPMSS = 1460;
+
+// Maximum size of the initial congestion window in packets.
+const size_t kDefaultInitialWindow = 10;
+const uint32 kMaxInitialWindow = 100;
+
+// Default size of initial flow control window, for both stream and session.
+const uint32 kDefaultFlowControlSendWindow = 16 * 1024;  // 16 KB
+
+// Maximum size of the congestion window, in packets, for TCP congestion control
+// algorithms.
+const size_t kMaxTcpCongestionWindow = 200;
+
+// Size of the socket receive buffer in bytes.
+const QuicByteCount kDefaultSocketReceiveBuffer = 256 * 1024;
+
+// Don't allow a client to suggest an RTT longer than 15 seconds.
+const uint32 kMaxInitialRoundTripTimeUs = 15 * kNumMicrosPerSecond;
+
+// Maximum number of open streams per connection.
+const size_t kDefaultMaxStreamsPerConnection = 100;
+
+// Number of bytes reserved for public flags in the packet header.
+const size_t kPublicFlagsSize = 1;
+// Number of bytes reserved for version number in the packet header.
+const size_t kQuicVersionSize = 4;
+// Number of bytes reserved for private flags in the packet header.
+const size_t kPrivateFlagsSize = 1;
+// Number of bytes reserved for FEC group in the packet header.
+const size_t kFecGroupSize = 1;
+
+// Signifies that the QuicPacket will contain version of the protocol.
+const bool kIncludeVersion = true;
+
+// Index of the first byte in a QUIC packet which is used in hash calculation.
+const size_t kStartOfHashData = 0;
+
+// Limit on the delta between stream IDs.
+const QuicStreamId kMaxStreamIdDelta = 200;
+// Limit on the delta between header IDs.
+const QuicHeaderId kMaxHeaderIdDelta = 200;
+
+// Reserved ID for the crypto stream.
+const QuicStreamId kCryptoStreamId = 1;
+
+// Reserved ID for the headers stream.
+const QuicStreamId kHeadersStreamId = 3;
+
+// Maximum delayed ack time, in ms.
+const int kMaxDelayedAckTimeMs = 25;
+
+// The timeout before the handshake succeeds.
+const int64 kInitialIdleTimeoutSecs = 5;
+// The default idle timeout.
+const int64 kDefaultIdleTimeoutSecs = 30;
+// The maximum idle timeout that can be negotiated.
+const int64 kMaximumIdleTimeoutSecs = 60 * 10;  // 10 minutes.
+// The default timeout for a connection until the crypto handshake succeeds.
+const int64 kMaxTimeForCryptoHandshakeSecs = 10;  // 10 secs.
+
+// Default ping timeout.
+const int64 kPingTimeoutSecs = 15;  // 15 secs.
+
+// Minimum number of RTTs between Server Config Updates (SCUP) sent to client.
+const int kMinIntervalBetweenServerConfigUpdatesRTTs = 10;
+
+// Minimum time between Server Config Updates (SCUP) sent to client.
+const int kMinIntervalBetweenServerConfigUpdatesMs = 1000;
+
+// The number of open streams that a server will accept is set to be slightly
+// larger than the negotiated limit. Immediately closing the connection if the
+// client opens slightly too many streams is not ideal: the client may have sent
+// a FIN that was lost, and simultaneously opened a new stream. The number of
+// streams a server accepts is a fixed increment over the negotiated limit, or a
+// percentage increase, whichever is larger.
+const float kMaxStreamsMultiplier = 1.1f;
+const int kMaxStreamsMinimumIncrement = 10;
+
+// We define an unsigned 16-bit floating point value, inspired by IEEE floats
+// (http://en.wikipedia.org/wiki/Half_precision_floating-point_format),
+// with 5-bit exponent (bias 1), 11-bit mantissa (effective 12 with hidden
+// bit) and denormals, but without signs, transfinites or fractions. Wire format
+// 16 bits (little-endian byte order) are split into exponent (high 5) and
+// mantissa (low 11) and decoded as:
+//   uint64 value;
+//   if (exponent == 0) value = mantissa;
+//   else value = (mantissa | 1 << 11) << (exponent - 1)
+const int kUFloat16ExponentBits = 5;
+const int kUFloat16MaxExponent = (1 << kUFloat16ExponentBits) - 2;  // 30
+const int kUFloat16MantissaBits = 16 - kUFloat16ExponentBits;  // 11
+const int kUFloat16MantissaEffectiveBits = kUFloat16MantissaBits + 1;  // 12
+const uint64 kUFloat16MaxValue =  // 0x3FFC0000000
+    ((GG_UINT64_C(1) << kUFloat16MantissaEffectiveBits) - 1) <<
+    kUFloat16MaxExponent;
+
+enum TransmissionType {
+  NOT_RETRANSMISSION,
+  FIRST_TRANSMISSION_TYPE = NOT_RETRANSMISSION,
+  HANDSHAKE_RETRANSMISSION,  // Retransmits due to handshake timeouts.
+  ALL_UNACKED_RETRANSMISSION,  // Retransmits all unacked packets.
+  ALL_INITIAL_RETRANSMISSION,  // Retransmits all initially encrypted packets.
+  LOSS_RETRANSMISSION,  // Retransmits due to loss detection.
+  RTO_RETRANSMISSION,  // Retransmits due to retransmit time out.
+  TLP_RETRANSMISSION,  // Tail loss probes.
+  LAST_TRANSMISSION_TYPE = TLP_RETRANSMISSION,
+};
+
+enum HasRetransmittableData {
+  NO_RETRANSMITTABLE_DATA,
+  HAS_RETRANSMITTABLE_DATA,
+};
+
+enum IsHandshake {
+  NOT_HANDSHAKE,
+  IS_HANDSHAKE
+};
+
+// Indicates FEC protection level for data being written.
+enum FecProtection {
+  MUST_FEC_PROTECT,  // Callee must FEC protect this data.
+  MAY_FEC_PROTECT    // Callee does not have to but may FEC protect this data.
+};
+
+// Indicates FEC policy.
+enum FecPolicy {
+  FEC_PROTECT_ALWAYS,   // All data in the stream should be FEC protected.
+  FEC_PROTECT_OPTIONAL  // Data in the stream does not need FEC protection.
+};
+
+enum QuicFrameType {
+  // Regular frame types. The values set here cannot change without the
+  // introduction of a new QUIC version.
+  PADDING_FRAME = 0,
+  RST_STREAM_FRAME = 1,
+  CONNECTION_CLOSE_FRAME = 2,
+  GOAWAY_FRAME = 3,
+  WINDOW_UPDATE_FRAME = 4,
+  BLOCKED_FRAME = 5,
+  STOP_WAITING_FRAME = 6,
+  PING_FRAME = 7,
+
+  // STREAM, ACK, and CONGESTION_FEEDBACK frames are special frames. They are
+  // encoded differently on the wire and their values do not need to be stable.
+  STREAM_FRAME,
+  ACK_FRAME,
+  CONGESTION_FEEDBACK_FRAME,
+  NUM_FRAME_TYPES
+};
+
+enum QuicConnectionIdLength {
+  PACKET_0BYTE_CONNECTION_ID = 0,
+  PACKET_1BYTE_CONNECTION_ID = 1,
+  PACKET_4BYTE_CONNECTION_ID = 4,
+  PACKET_8BYTE_CONNECTION_ID = 8
+};
+
+enum InFecGroup {
+  NOT_IN_FEC_GROUP,
+  IN_FEC_GROUP,
+};
+
+enum QuicSequenceNumberLength {
+  PACKET_1BYTE_SEQUENCE_NUMBER = 1,
+  PACKET_2BYTE_SEQUENCE_NUMBER = 2,
+  PACKET_4BYTE_SEQUENCE_NUMBER = 4,
+  PACKET_6BYTE_SEQUENCE_NUMBER = 6
+};
+
+// Used to indicate a QuicSequenceNumberLength using two flag bits.
+enum QuicSequenceNumberLengthFlags {
+  PACKET_FLAGS_1BYTE_SEQUENCE = 0,  // 00
+  PACKET_FLAGS_2BYTE_SEQUENCE = 1,  // 01
+  PACKET_FLAGS_4BYTE_SEQUENCE = 1 << 1,  // 10
+  PACKET_FLAGS_6BYTE_SEQUENCE = 1 << 1 | 1,  // 11
+};
+
+// The public flags are specified in one byte.
+enum QuicPacketPublicFlags {
+  PACKET_PUBLIC_FLAGS_NONE = 0,
+
+  // Bit 0: Does the packet header contains version info?
+  PACKET_PUBLIC_FLAGS_VERSION = 1 << 0,
+
+  // Bit 1: Is this packet a public reset packet?
+  PACKET_PUBLIC_FLAGS_RST = 1 << 1,
+
+  // Bits 2 and 3 specify the length of the ConnectionId as follows:
+  // ----00--: 0 bytes
+  // ----01--: 1 byte
+  // ----10--: 4 bytes
+  // ----11--: 8 bytes
+  PACKET_PUBLIC_FLAGS_0BYTE_CONNECTION_ID = 0,
+  PACKET_PUBLIC_FLAGS_1BYTE_CONNECTION_ID = 1 << 2,
+  PACKET_PUBLIC_FLAGS_4BYTE_CONNECTION_ID = 1 << 3,
+  PACKET_PUBLIC_FLAGS_8BYTE_CONNECTION_ID = 1 << 3 | 1 << 2,
+
+  // Bits 4 and 5 describe the packet sequence number length as follows:
+  // --00----: 1 byte
+  // --01----: 2 bytes
+  // --10----: 4 bytes
+  // --11----: 6 bytes
+  PACKET_PUBLIC_FLAGS_1BYTE_SEQUENCE = PACKET_FLAGS_1BYTE_SEQUENCE << 4,
+  PACKET_PUBLIC_FLAGS_2BYTE_SEQUENCE = PACKET_FLAGS_2BYTE_SEQUENCE << 4,
+  PACKET_PUBLIC_FLAGS_4BYTE_SEQUENCE = PACKET_FLAGS_4BYTE_SEQUENCE << 4,
+  PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE = PACKET_FLAGS_6BYTE_SEQUENCE << 4,
+
+  // All bits set (bits 6 and 7 are not currently used): 00111111
+  PACKET_PUBLIC_FLAGS_MAX = (1 << 6) - 1
+};
+
+// The private flags are specified in one byte.
+enum QuicPacketPrivateFlags {
+  PACKET_PRIVATE_FLAGS_NONE = 0,
+
+  // Bit 0: Does this packet contain an entropy bit?
+  PACKET_PRIVATE_FLAGS_ENTROPY = 1 << 0,
+
+  // Bit 1: Payload is part of an FEC group?
+  PACKET_PRIVATE_FLAGS_FEC_GROUP = 1 << 1,
+
+  // Bit 2: Payload is FEC as opposed to frames?
+  PACKET_PRIVATE_FLAGS_FEC = 1 << 2,
+
+  // All bits set (bits 3-7 are not currently used): 00000111
+  PACKET_PRIVATE_FLAGS_MAX = (1 << 3) - 1
+};
+
+// The available versions of QUIC. Guaranteed that the integer value of the enum
+// will match the version number.
+// When adding a new version to this enum you should add it to
+// kSupportedQuicVersions (if appropriate), and also add a new case to the
+// helper methods QuicVersionToQuicTag, QuicTagToQuicVersion, and
+// QuicVersionToString.
+enum QuicVersion {
+  // Special case to indicate unknown/unsupported QUIC version.
+  QUIC_VERSION_UNSUPPORTED = 0,
+
+  QUIC_VERSION_18 = 18,  // PING frame.
+  QUIC_VERSION_19 = 19,  // Connection level flow control.
+  QUIC_VERSION_21 = 21,  // Headers/crypto streams are flow controlled.
+  QUIC_VERSION_22 = 22,  // Send Server Config Update messages on crypto stream.
+  QUIC_VERSION_23 = 23,  // Timestamp in the ack frame.
+};
+
+// This vector contains QUIC versions which we currently support.
+// This should be ordered such that the highest supported version is the first
+// element, with subsequent elements in descending order (versions can be
+// skipped as necessary).
+//
+// IMPORTANT: if you are adding to this list, follow the instructions at
+// http://sites/quic/adding-and-removing-versions
+static const QuicVersion kSupportedQuicVersions[] = {QUIC_VERSION_23,
+                                                     QUIC_VERSION_22,
+                                                     QUIC_VERSION_21,
+                                                     QUIC_VERSION_19,
+                                                     QUIC_VERSION_18};
+
+typedef std::vector<QuicVersion> QuicVersionVector;
+
+// Returns a vector of QUIC versions in kSupportedQuicVersions.
+NET_EXPORT_PRIVATE QuicVersionVector QuicSupportedVersions();
+
+// QuicTag is written to and read from the wire, but we prefer to use
+// the more readable QuicVersion at other levels.
+// Helper function which translates from a QuicVersion to a QuicTag. Returns 0
+// if QuicVersion is unsupported.
+NET_EXPORT_PRIVATE QuicTag QuicVersionToQuicTag(const QuicVersion version);
+
+// Returns appropriate QuicVersion from a QuicTag.
+// Returns QUIC_VERSION_UNSUPPORTED if version_tag cannot be understood.
+NET_EXPORT_PRIVATE QuicVersion QuicTagToQuicVersion(const QuicTag version_tag);
+
+// Helper function which translates from a QuicVersion to a string.
+// Returns strings corresponding to enum names (e.g. QUIC_VERSION_6).
+NET_EXPORT_PRIVATE std::string QuicVersionToString(const QuicVersion version);
+
+// Returns comma separated list of string representations of QuicVersion enum
+// values in the supplied |versions| vector.
+NET_EXPORT_PRIVATE std::string QuicVersionVectorToString(
+    const QuicVersionVector& versions);
+
+// Version and Crypto tags are written to the wire with a big-endian
+// representation of the name of the tag.  For example
+// the client hello tag (CHLO) will be written as the
+// following 4 bytes: 'C' 'H' 'L' 'O'.  Since it is
+// stored in memory as a little endian uint32, we need
+// to reverse the order of the bytes.
+
+// MakeQuicTag returns a value given the four bytes. For example:
+//   MakeQuicTag('C', 'H', 'L', 'O');
+NET_EXPORT_PRIVATE QuicTag MakeQuicTag(char a, char b, char c, char d);
+
+// Returns true if the tag vector contains the specified tag.
+NET_EXPORT_PRIVATE bool ContainsQuicTag(const QuicTagVector& tag_vector,
+                                        QuicTag tag);
+
+// Size in bytes of the data or fec packet header.
+NET_EXPORT_PRIVATE size_t GetPacketHeaderSize(const QuicPacketHeader& header);
+
+NET_EXPORT_PRIVATE size_t GetPacketHeaderSize(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length,
+    InFecGroup is_in_fec_group);
+
+// Index of the first byte in a QUIC packet of FEC protected data.
+NET_EXPORT_PRIVATE size_t GetStartOfFecProtectedData(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length);
+// Index of the first byte in a QUIC packet of encrypted data.
+NET_EXPORT_PRIVATE size_t GetStartOfEncryptedData(
+    QuicConnectionIdLength connection_id_length,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length);
+
+enum QuicRstStreamErrorCode {
+  QUIC_STREAM_NO_ERROR = 0,
+
+  // There was some error which halted stream processing.
+  QUIC_ERROR_PROCESSING_STREAM,
+  // We got two fin or reset offsets which did not match.
+  QUIC_MULTIPLE_TERMINATION_OFFSETS,
+  // We got bad payload and can not respond to it at the protocol level.
+  QUIC_BAD_APPLICATION_PAYLOAD,
+  // Stream closed due to connection error. No reset frame is sent when this
+  // happens.
+  QUIC_STREAM_CONNECTION_ERROR,
+  // GoAway frame sent. No more stream can be created.
+  QUIC_STREAM_PEER_GOING_AWAY,
+  // The stream has been cancelled.
+  QUIC_STREAM_CANCELLED,
+  // Sending a RST to allow for proper flow control accounting.
+  QUIC_RST_FLOW_CONTROL_ACCOUNTING,
+
+  // No error. Used as bound while iterating.
+  QUIC_STREAM_LAST_ERROR,
+};
+
+// Because receiving an unknown QuicRstStreamErrorCode results in connection
+// teardown, we use this to make sure any errors predating a given version are
+// downgraded to the most appropriate existing error.
+NET_EXPORT_PRIVATE QuicRstStreamErrorCode AdjustErrorForVersion(
+    QuicRstStreamErrorCode error_code,
+    QuicVersion version);
+
+// These values must remain stable as they are uploaded to UMA histograms.
+// To add a new error code, use the current value of QUIC_LAST_ERROR and
+// increment QUIC_LAST_ERROR.
+enum QuicErrorCode {
+  QUIC_NO_ERROR = 0,
+
+  // Connection has reached an invalid state.
+  QUIC_INTERNAL_ERROR = 1,
+  // There were data frames after the a fin or reset.
+  QUIC_STREAM_DATA_AFTER_TERMINATION = 2,
+  // Control frame is malformed.
+  QUIC_INVALID_PACKET_HEADER = 3,
+  // Frame data is malformed.
+  QUIC_INVALID_FRAME_DATA = 4,
+  // The packet contained no payload.
+  QUIC_MISSING_PAYLOAD = 48,
+  // FEC data is malformed.
+  QUIC_INVALID_FEC_DATA = 5,
+  // STREAM frame data is malformed.
+  QUIC_INVALID_STREAM_DATA = 46,
+  // STREAM frame data is not encrypted.
+  QUIC_UNENCRYPTED_STREAM_DATA = 61,
+  // RST_STREAM frame data is malformed.
+  QUIC_INVALID_RST_STREAM_DATA = 6,
+  // CONNECTION_CLOSE frame data is malformed.
+  QUIC_INVALID_CONNECTION_CLOSE_DATA = 7,
+  // GOAWAY frame data is malformed.
+  QUIC_INVALID_GOAWAY_DATA = 8,
+  // WINDOW_UPDATE frame data is malformed.
+  QUIC_INVALID_WINDOW_UPDATE_DATA = 57,
+  // BLOCKED frame data is malformed.
+  QUIC_INVALID_BLOCKED_DATA = 58,
+  // STOP_WAITING frame data is malformed.
+  QUIC_INVALID_STOP_WAITING_DATA = 60,
+  // ACK frame data is malformed.
+  QUIC_INVALID_ACK_DATA = 9,
+  // CONGESTION_FEEDBACK frame data is malformed.
+  QUIC_INVALID_CONGESTION_FEEDBACK_DATA = 47,
+  // Version negotiation packet is malformed.
+  QUIC_INVALID_VERSION_NEGOTIATION_PACKET = 10,
+  // Public RST packet is malformed.
+  QUIC_INVALID_PUBLIC_RST_PACKET = 11,
+  // There was an error decrypting.
+  QUIC_DECRYPTION_FAILURE = 12,
+  // There was an error encrypting.
+  QUIC_ENCRYPTION_FAILURE = 13,
+  // The packet exceeded kMaxPacketSize.
+  QUIC_PACKET_TOO_LARGE = 14,
+  // Data was sent for a stream which did not exist.
+  QUIC_PACKET_FOR_NONEXISTENT_STREAM = 15,
+  // The peer is going away.  May be a client or server.
+  QUIC_PEER_GOING_AWAY = 16,
+  // A stream ID was invalid.
+  QUIC_INVALID_STREAM_ID = 17,
+  // A priority was invalid.
+  QUIC_INVALID_PRIORITY = 49,
+  // Too many streams already open.
+  QUIC_TOO_MANY_OPEN_STREAMS = 18,
+  // The peer must send a FIN/RST for each stream, and has not been doing so.
+  QUIC_TOO_MANY_UNFINISHED_STREAMS = 66,
+  // Received public reset for this connection.
+  QUIC_PUBLIC_RESET = 19,
+  // Invalid protocol version.
+  QUIC_INVALID_VERSION = 20,
+
+  // deprecated: QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED = 21
+
+  // The Header ID for a stream was too far from the previous.
+  QUIC_INVALID_HEADER_ID = 22,
+  // Negotiable parameter received during handshake had invalid value.
+  QUIC_INVALID_NEGOTIATED_VALUE = 23,
+  // There was an error decompressing data.
+  QUIC_DECOMPRESSION_FAILURE = 24,
+  // We hit our prenegotiated (or default) timeout
+  QUIC_CONNECTION_TIMED_OUT = 25,
+  // We hit our overall connection timeout
+  QUIC_CONNECTION_OVERALL_TIMED_OUT = 67,
+  // There was an error encountered migrating addresses
+  QUIC_ERROR_MIGRATING_ADDRESS = 26,
+  // There was an error while writing to the socket.
+  QUIC_PACKET_WRITE_ERROR = 27,
+  // There was an error while reading from the socket.
+  QUIC_PACKET_READ_ERROR = 51,
+  // We received a STREAM_FRAME with no data and no fin flag set.
+  QUIC_INVALID_STREAM_FRAME = 50,
+  // We received invalid data on the headers stream.
+  QUIC_INVALID_HEADERS_STREAM_DATA = 56,
+  // The peer received too much data, violating flow control.
+  QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA = 59,
+  // The peer sent too much data, violating flow control.
+  QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA = 63,
+  // The peer received an invalid flow control window.
+  QUIC_FLOW_CONTROL_INVALID_WINDOW = 64,
+  // The connection has been IP pooled into an existing connection.
+  QUIC_CONNECTION_IP_POOLED = 62,
+
+  // Crypto errors.
+
+  // Hanshake failed.
+  QUIC_HANDSHAKE_FAILED = 28,
+  // Handshake message contained out of order tags.
+  QUIC_CRYPTO_TAGS_OUT_OF_ORDER = 29,
+  // Handshake message contained too many entries.
+  QUIC_CRYPTO_TOO_MANY_ENTRIES = 30,
+  // Handshake message contained an invalid value length.
+  QUIC_CRYPTO_INVALID_VALUE_LENGTH = 31,
+  // A crypto message was received after the handshake was complete.
+  QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE = 32,
+  // A crypto message was received with an illegal message tag.
+  QUIC_INVALID_CRYPTO_MESSAGE_TYPE = 33,
+  // A crypto message was received with an illegal parameter.
+  QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER = 34,
+  // An invalid channel id signature was supplied.
+  QUIC_INVALID_CHANNEL_ID_SIGNATURE = 52,
+  // A crypto message was received with a mandatory parameter missing.
+  QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND = 35,
+  // A crypto message was received with a parameter that has no overlap
+  // with the local parameter.
+  QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP = 36,
+  // A crypto message was received that contained a parameter with too few
+  // values.
+  QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND = 37,
+  // An internal error occured in crypto processing.
+  QUIC_CRYPTO_INTERNAL_ERROR = 38,
+  // A crypto handshake message specified an unsupported version.
+  QUIC_CRYPTO_VERSION_NOT_SUPPORTED = 39,
+  // There was no intersection between the crypto primitives supported by the
+  // peer and ourselves.
+  QUIC_CRYPTO_NO_SUPPORT = 40,
+  // The server rejected our client hello messages too many times.
+  QUIC_CRYPTO_TOO_MANY_REJECTS = 41,
+  // The client rejected the server's certificate chain or signature.
+  QUIC_PROOF_INVALID = 42,
+  // A crypto message was received with a duplicate tag.
+  QUIC_CRYPTO_DUPLICATE_TAG = 43,
+  // A crypto message was received with the wrong encryption level (i.e. it
+  // should have been encrypted but was not.)
+  QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT = 44,
+  // The server config for a server has expired.
+  QUIC_CRYPTO_SERVER_CONFIG_EXPIRED = 45,
+  // We failed to setup the symmetric keys for a connection.
+  QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED = 53,
+  // A handshake message arrived, but we are still validating the
+  // previous handshake message.
+  QUIC_CRYPTO_MESSAGE_WHILE_VALIDATING_CLIENT_HELLO = 54,
+  // A server config update arrived before the handshake is complete.
+  QUIC_CRYPTO_UPDATE_BEFORE_HANDSHAKE_COMPLETE = 65,
+  // This connection involved a version negotiation which appears to have been
+  // tampered with.
+  QUIC_VERSION_NEGOTIATION_MISMATCH = 55,
+
+  // No error. Used as bound while iterating.
+  QUIC_LAST_ERROR = 68,
+};
+
+struct NET_EXPORT_PRIVATE QuicPacketPublicHeader {
+  QuicPacketPublicHeader();
+  explicit QuicPacketPublicHeader(const QuicPacketPublicHeader& other);
+  ~QuicPacketPublicHeader();
+
+  // Universal header. All QuicPacket headers will have a connection_id and
+  // public flags.
+  QuicConnectionId connection_id;
+  QuicConnectionIdLength connection_id_length;
+  bool reset_flag;
+  bool version_flag;
+  QuicSequenceNumberLength sequence_number_length;
+  QuicVersionVector versions;
+};
+
+// Header for Data or FEC packets.
+struct NET_EXPORT_PRIVATE QuicPacketHeader {
+  QuicPacketHeader();
+  explicit QuicPacketHeader(const QuicPacketPublicHeader& header);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicPacketHeader& s);
+
+  QuicPacketPublicHeader public_header;
+  bool fec_flag;
+  bool entropy_flag;
+  QuicPacketEntropyHash entropy_hash;
+  QuicPacketSequenceNumber packet_sequence_number;
+  InFecGroup is_in_fec_group;
+  QuicFecGroupNumber fec_group;
+};
+
+struct NET_EXPORT_PRIVATE QuicPublicResetPacket {
+  QuicPublicResetPacket();
+  explicit QuicPublicResetPacket(const QuicPacketPublicHeader& header);
+
+  QuicPacketPublicHeader public_header;
+  QuicPublicResetNonceProof nonce_proof;
+  QuicPacketSequenceNumber rejected_sequence_number;
+  IPEndPoint client_address;
+};
+
+enum QuicVersionNegotiationState {
+  START_NEGOTIATION = 0,
+  // Server-side this implies we've sent a version negotiation packet and are
+  // waiting on the client to select a compatible version.  Client-side this
+  // implies we've gotten a version negotiation packet, are retransmitting the
+  // initial packets with a supported version and are waiting for our first
+  // packet from the server.
+  NEGOTIATION_IN_PROGRESS,
+  // This indicates this endpoint has received a packet from the peer with a
+  // version this endpoint supports.  Version negotiation is complete, and the
+  // version number will no longer be sent with future packets.
+  NEGOTIATED_VERSION
+};
+
+typedef QuicPacketPublicHeader QuicVersionNegotiationPacket;
+
+// A padding frame contains no payload.
+struct NET_EXPORT_PRIVATE QuicPaddingFrame {
+};
+
+// A ping frame contains no payload, though it is retransmittable,
+// and ACK'd just like other normal frames.
+struct NET_EXPORT_PRIVATE QuicPingFrame {
+};
+
+struct NET_EXPORT_PRIVATE QuicStreamFrame {
+  QuicStreamFrame();
+  QuicStreamFrame(const QuicStreamFrame& frame);
+  QuicStreamFrame(QuicStreamId stream_id,
+                  bool fin,
+                  QuicStreamOffset offset,
+                  IOVector data);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicStreamFrame& s);
+
+  // Returns a copy of the IOVector |data| as a heap-allocated string.
+  // Caller must take ownership of the returned string.
+  std::string* GetDataAsString() const;
+
+  QuicStreamId stream_id;
+  bool fin;
+  QuicStreamOffset offset;  // Location of this data in the stream.
+  IOVector data;
+
+  // If this is set, then when this packet is ACKed the AckNotifier will be
+  // informed.
+  QuicAckNotifier* notifier;
+};
+
+// TODO(ianswett): Re-evaluate the trade-offs of hash_set vs set when framing
+// is finalized.
+typedef std::set<QuicPacketSequenceNumber> SequenceNumberSet;
+typedef std::list<QuicPacketSequenceNumber> SequenceNumberList;
+
+typedef std::list<
+    std::pair<QuicPacketSequenceNumber, QuicTime> > PacketTimeList;
+
+struct NET_EXPORT_PRIVATE QuicStopWaitingFrame {
+  QuicStopWaitingFrame();
+  ~QuicStopWaitingFrame();
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicStopWaitingFrame& s);
+  // Entropy hash of all packets up to, but not including, the least unacked
+  // packet.
+  QuicPacketEntropyHash entropy_hash;
+  // The lowest packet we've sent which is unacked, and we expect an ack for.
+  QuicPacketSequenceNumber least_unacked;
+};
+
+struct NET_EXPORT_PRIVATE QuicAckFrame {
+  QuicAckFrame();
+  ~QuicAckFrame();
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicAckFrame& s);
+
+  // Entropy hash of all packets up to largest observed not including missing
+  // packets.
+  QuicPacketEntropyHash entropy_hash;
+
+  // The highest packet sequence number we've observed from the peer.
+  //
+  // In general, this should be the largest packet number we've received.  In
+  // the case of truncated acks, we may have to advertise a lower "upper bound"
+  // than largest received, to avoid implicitly acking missing packets that
+  // don't fit in the missing packet list due to size limitations.  In this
+  // case, largest_observed may be a packet which is also in the missing packets
+  // list.
+  QuicPacketSequenceNumber largest_observed;
+
+  // Time elapsed since largest_observed was received until this Ack frame was
+  // sent.
+  QuicTime::Delta delta_time_largest_observed;
+
+  // TODO(satyamshekhar): Can be optimized using an interval set like data
+  // structure.
+  // The set of packets which we're expecting and have not received.
+  SequenceNumberSet missing_packets;
+
+  // Whether the ack had to be truncated when sent.
+  bool is_truncated;
+
+  // Packets which have been revived via FEC.
+  // All of these must also be in missing_packets.
+  SequenceNumberSet revived_packets;
+
+  // List of <sequence_number, time> for when packets arrived.
+  PacketTimeList received_packet_times;
+};
+
+// True if the sequence number is greater than largest_observed or is listed
+// as missing.
+// Always returns false for sequence numbers less than least_unacked.
+bool NET_EXPORT_PRIVATE IsAwaitingPacket(
+    const QuicAckFrame& ack_frame,
+    QuicPacketSequenceNumber sequence_number);
+
+// Inserts missing packets between [lower, higher).
+void NET_EXPORT_PRIVATE InsertMissingPacketsBetween(
+    QuicAckFrame* ack_frame,
+    QuicPacketSequenceNumber lower,
+    QuicPacketSequenceNumber higher);
+
+// Defines for all types of congestion feedback that will be negotiated in QUIC,
+// kTCP MUST be supported by all QUIC implementations to guarantee 100%
+// compatibility.
+// TODO(cyr): Remove this when removing QUIC_VERSION_22.
+enum CongestionFeedbackType {
+  kTCP,  // Used to mimic TCP.
+};
+
+// Defines for all types of congestion control algorithms that can be used in
+// QUIC. Note that this is separate from the congestion feedback type -
+// some congestion control algorithms may use the same feedback type
+// (Reno and Cubic are the classic example for that).
+enum CongestionControlType {
+  kCubic,
+  kReno,
+  kBBR,
+};
+
+enum LossDetectionType {
+  kNack,  // Used to mimic TCP's loss detection.
+  kTime,  // Time based loss detection.
+};
+
+// TODO(cyr): Remove this when removing QUIC_VERSION_22.
+struct NET_EXPORT_PRIVATE CongestionFeedbackMessageTCP {
+  CongestionFeedbackMessageTCP();
+
+  QuicByteCount receive_window;
+};
+
+// TODO(cyr): Remove this when removing QUIC_VERSION_22.
+struct NET_EXPORT_PRIVATE QuicCongestionFeedbackFrame {
+  QuicCongestionFeedbackFrame();
+  ~QuicCongestionFeedbackFrame();
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicCongestionFeedbackFrame& c);
+
+  CongestionFeedbackType type;
+  // This should really be a union, but since the timestamp struct
+  // is non-trivial, C++ prohibits it.
+  CongestionFeedbackMessageTCP tcp;
+};
+
+struct NET_EXPORT_PRIVATE QuicRstStreamFrame {
+  QuicRstStreamFrame();
+  QuicRstStreamFrame(QuicStreamId stream_id,
+                     QuicRstStreamErrorCode error_code,
+                     QuicStreamOffset bytes_written);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicRstStreamFrame& r);
+
+  QuicStreamId stream_id;
+  QuicRstStreamErrorCode error_code;
+  std::string error_details;
+
+  // Used to update flow control windows. On termination of a stream, both
+  // endpoints must inform the peer of the number of bytes they have sent on
+  // that stream. This can be done through normal termination (data packet with
+  // FIN) or through a RST.
+  QuicStreamOffset byte_offset;
+};
+
+struct NET_EXPORT_PRIVATE QuicConnectionCloseFrame {
+  QuicConnectionCloseFrame();
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicConnectionCloseFrame& c);
+
+  QuicErrorCode error_code;
+  std::string error_details;
+};
+
+struct NET_EXPORT_PRIVATE QuicGoAwayFrame {
+  QuicGoAwayFrame();
+  QuicGoAwayFrame(QuicErrorCode error_code,
+                  QuicStreamId last_good_stream_id,
+                  const std::string& reason);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicGoAwayFrame& g);
+
+  QuicErrorCode error_code;
+  QuicStreamId last_good_stream_id;
+  std::string reason_phrase;
+};
+
+// Flow control updates per-stream and at the connection levoel.
+// Based on SPDY's WINDOW_UPDATE frame, but uses an absolute byte offset rather
+// than a window delta.
+// TODO(rjshade): A possible future optimization is to make stream_id and
+//                byte_offset variable length, similar to stream frames.
+struct NET_EXPORT_PRIVATE QuicWindowUpdateFrame {
+  QuicWindowUpdateFrame() {}
+  QuicWindowUpdateFrame(QuicStreamId stream_id, QuicStreamOffset byte_offset);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicWindowUpdateFrame& w);
+
+  // The stream this frame applies to.  0 is a special case meaning the overall
+  // connection rather than a specific stream.
+  QuicStreamId stream_id;
+
+  // Byte offset in the stream or connection. The receiver of this frame must
+  // not send data which would result in this offset being exceeded.
+  QuicStreamOffset byte_offset;
+};
+
+// The BLOCKED frame is used to indicate to the remote endpoint that this
+// endpoint believes itself to be flow-control blocked but otherwise ready to
+// send data. The BLOCKED frame is purely advisory and optional.
+// Based on SPDY's BLOCKED frame (undocumented as of 2014-01-28).
+struct NET_EXPORT_PRIVATE QuicBlockedFrame {
+  QuicBlockedFrame() {}
+  explicit QuicBlockedFrame(QuicStreamId stream_id);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicBlockedFrame& b);
+
+  // The stream this frame applies to.  0 is a special case meaning the overall
+  // connection rather than a specific stream.
+  QuicStreamId stream_id;
+};
+
+// EncryptionLevel enumerates the stages of encryption that a QUIC connection
+// progresses through. When retransmitting a packet, the encryption level needs
+// to be specified so that it is retransmitted at a level which the peer can
+// understand.
+enum EncryptionLevel {
+  ENCRYPTION_NONE = 0,
+  ENCRYPTION_INITIAL = 1,
+  ENCRYPTION_FORWARD_SECURE = 2,
+
+  NUM_ENCRYPTION_LEVELS,
+};
+
+struct NET_EXPORT_PRIVATE QuicFrame {
+  QuicFrame();
+  explicit QuicFrame(QuicPaddingFrame* padding_frame);
+  explicit QuicFrame(QuicStreamFrame* stream_frame);
+  explicit QuicFrame(QuicAckFrame* frame);
+
+  // TODO(cyr): Remove this when removing QUIC_VERSION_22.
+  explicit QuicFrame(QuicCongestionFeedbackFrame* frame);
+
+  explicit QuicFrame(QuicRstStreamFrame* frame);
+  explicit QuicFrame(QuicConnectionCloseFrame* frame);
+  explicit QuicFrame(QuicStopWaitingFrame* frame);
+  explicit QuicFrame(QuicPingFrame* frame);
+  explicit QuicFrame(QuicGoAwayFrame* frame);
+  explicit QuicFrame(QuicWindowUpdateFrame* frame);
+  explicit QuicFrame(QuicBlockedFrame* frame);
+
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicFrame& frame);
+
+  QuicFrameType type;
+  union {
+    QuicPaddingFrame* padding_frame;
+    QuicStreamFrame* stream_frame;
+    QuicAckFrame* ack_frame;
+
+    // TODO(cyr): Remove this when removing QUIC_VERSION_22.
+    QuicCongestionFeedbackFrame* congestion_feedback_frame;
+    QuicStopWaitingFrame* stop_waiting_frame;
+
+    QuicPingFrame* ping_frame;
+    QuicRstStreamFrame* rst_stream_frame;
+    QuicConnectionCloseFrame* connection_close_frame;
+    QuicGoAwayFrame* goaway_frame;
+    QuicWindowUpdateFrame* window_update_frame;
+    QuicBlockedFrame* blocked_frame;
+  };
+};
+
+typedef std::vector<QuicFrame> QuicFrames;
+
+struct NET_EXPORT_PRIVATE QuicFecData {
+  QuicFecData();
+
+  // The FEC group number is also the sequence number of the first
+  // FEC protected packet.  The last protected packet's sequence number will
+  // be one less than the sequence number of the FEC packet.
+  QuicFecGroupNumber fec_group;
+  base::StringPiece redundancy;
+};
+
+class NET_EXPORT_PRIVATE QuicData {
+ public:
+  QuicData(const char* buffer, size_t length);
+  QuicData(char* buffer, size_t length, bool owns_buffer);
+  virtual ~QuicData();
+
+  base::StringPiece AsStringPiece() const {
+    return base::StringPiece(data(), length());
+  }
+
+  const char* data() const { return buffer_; }
+  size_t length() const { return length_; }
+
+ private:
+  const char* buffer_;
+  size_t length_;
+  bool owns_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicData);
+};
+
+class NET_EXPORT_PRIVATE QuicPacket : public QuicData {
+ public:
+  static QuicPacket* NewDataPacket(
+      char* buffer,
+      size_t length,
+      bool owns_buffer,
+      QuicConnectionIdLength connection_id_length,
+      bool includes_version,
+      QuicSequenceNumberLength sequence_number_length) {
+    return new QuicPacket(buffer, length, owns_buffer, connection_id_length,
+                          includes_version, sequence_number_length, false);
+  }
+
+  static QuicPacket* NewFecPacket(
+      char* buffer,
+      size_t length,
+      bool owns_buffer,
+      QuicConnectionIdLength connection_id_length,
+      bool includes_version,
+      QuicSequenceNumberLength sequence_number_length) {
+    return new QuicPacket(buffer, length, owns_buffer, connection_id_length,
+                          includes_version, sequence_number_length, true);
+  }
+
+  base::StringPiece FecProtectedData() const;
+  base::StringPiece AssociatedData() const;
+  base::StringPiece BeforePlaintext() const;
+  base::StringPiece Plaintext() const;
+
+  bool is_fec_packet() const { return is_fec_packet_; }
+
+  char* mutable_data() { return buffer_; }
+
+ private:
+  QuicPacket(char* buffer,
+             size_t length,
+             bool owns_buffer,
+             QuicConnectionIdLength connection_id_length,
+             bool includes_version,
+             QuicSequenceNumberLength sequence_number_length,
+             bool is_fec_packet);
+
+  char* buffer_;
+  const bool is_fec_packet_;
+  const QuicConnectionIdLength connection_id_length_;
+  const bool includes_version_;
+  const QuicSequenceNumberLength sequence_number_length_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicPacket);
+};
+
+class NET_EXPORT_PRIVATE QuicEncryptedPacket : public QuicData {
+ public:
+  QuicEncryptedPacket(const char* buffer, size_t length);
+  QuicEncryptedPacket(char* buffer, size_t length, bool owns_buffer);
+
+  // Clones the packet into a new packet which owns the buffer.
+  QuicEncryptedPacket* Clone() const;
+
+  // By default, gtest prints the raw bytes of an object. The bool data
+  // member (in the base class QuicData) causes this object to have padding
+  // bytes, which causes the default gtest object printer to read
+  // uninitialize memory. So we need to teach gtest how to print this object.
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicEncryptedPacket& s);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicEncryptedPacket);
+};
+
+class NET_EXPORT_PRIVATE RetransmittableFrames {
+ public:
+  RetransmittableFrames();
+  ~RetransmittableFrames();
+
+  // Allocates a local copy of the referenced StringPiece has QuicStreamFrame
+  // use it.
+  // Takes ownership of |stream_frame|.
+  const QuicFrame& AddStreamFrame(QuicStreamFrame* stream_frame);
+  // Takes ownership of the frame inside |frame|.
+  const QuicFrame& AddNonStreamFrame(const QuicFrame& frame);
+  const QuicFrames& frames() const { return frames_; }
+
+  IsHandshake HasCryptoHandshake() const {
+    return has_crypto_handshake_;
+  }
+
+  void set_encryption_level(EncryptionLevel level);
+  EncryptionLevel encryption_level() const {
+    return encryption_level_;
+  }
+
+ private:
+  QuicFrames frames_;
+  EncryptionLevel encryption_level_;
+  IsHandshake has_crypto_handshake_;
+  // Data referenced by the StringPiece of a QuicStreamFrame.
+  std::vector<std::string*> stream_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(RetransmittableFrames);
+};
+
+struct NET_EXPORT_PRIVATE SerializedPacket {
+  SerializedPacket(QuicPacketSequenceNumber sequence_number,
+                   QuicSequenceNumberLength sequence_number_length,
+                   QuicPacket* packet,
+                   QuicPacketEntropyHash entropy_hash,
+                   RetransmittableFrames* retransmittable_frames);
+  ~SerializedPacket();
+
+  QuicPacketSequenceNumber sequence_number;
+  QuicSequenceNumberLength sequence_number_length;
+  QuicPacket* packet;
+  QuicPacketEntropyHash entropy_hash;
+  RetransmittableFrames* retransmittable_frames;
+
+  // If set, these will be called when this packet is ACKed by the peer.
+  std::set<QuicAckNotifier*> notifiers;
+};
+
+struct NET_EXPORT_PRIVATE TransmissionInfo {
+  // Used by STL when assigning into a map.
+  TransmissionInfo();
+
+  // Constructs a Transmission with a new all_tranmissions set
+  // containing |sequence_number|.
+  TransmissionInfo(RetransmittableFrames* retransmittable_frames,
+                   QuicSequenceNumberLength sequence_number_length);
+
+  // Constructs a Transmission with the specified |all_tranmissions| set
+  // and inserts |sequence_number| into it.
+  TransmissionInfo(RetransmittableFrames* retransmittable_frames,
+                   QuicSequenceNumberLength sequence_number_length,
+                   TransmissionType transmission_type,
+                   SequenceNumberList* all_transmissions);
+
+  RetransmittableFrames* retransmittable_frames;
+  QuicSequenceNumberLength sequence_number_length;
+  // Zero when the packet is serialized, non-zero once it's sent.
+  QuicTime sent_time;
+  // Zero when the packet is serialized, non-zero once it's sent.
+  QuicByteCount bytes_sent;
+  size_t nack_count;
+  // Reason why this packet was transmitted.
+  TransmissionType transmission_type;
+  // Stores the sequence numbers of all transmissions of this packet.
+  // Must always be nullptr or have multiple elements.
+  SequenceNumberList* all_transmissions;
+  // In flight packets have not been abandoned or lost.
+  bool in_flight;
+  // True if the packet can never be acked, so it can be removed.
+  bool is_unackable;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_PROTOCOL_H_
diff --git a/net/quic/quic_protocol_test.cc b/net/quic/quic_protocol_test.cc
new file mode 100644
index 0000000..4f23490
--- /dev/null
+++ b/net/quic/quic_protocol_test.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_protocol.h"
+
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicProtocolTest, AdjustErrorForVersion) {
+  ASSERT_EQ(8, QUIC_STREAM_LAST_ERROR)
+      << "Any additions to QuicRstStreamErrorCode require an addition to "
+      << "AdjustErrorForVersion and this associated test.";
+
+  EXPECT_EQ(QUIC_RST_FLOW_CONTROL_ACCOUNTING, AdjustErrorForVersion(
+      QUIC_RST_FLOW_CONTROL_ACCOUNTING,
+      QUIC_VERSION_18));
+}
+
+TEST(QuicProtocolTest, MakeQuicTag) {
+  QuicTag tag = MakeQuicTag('A', 'B', 'C', 'D');
+  char bytes[4];
+  memcpy(bytes, &tag, 4);
+  EXPECT_EQ('A', bytes[0]);
+  EXPECT_EQ('B', bytes[1]);
+  EXPECT_EQ('C', bytes[2]);
+  EXPECT_EQ('D', bytes[3]);
+}
+
+TEST(QuicProtocolTest, IsAawaitingPacket) {
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 10u;
+  EXPECT_TRUE(IsAwaitingPacket(ack_frame, 11u));
+  EXPECT_FALSE(IsAwaitingPacket(ack_frame, 1u));
+
+  ack_frame.missing_packets.insert(10);
+  EXPECT_TRUE(IsAwaitingPacket(ack_frame, 10u));
+}
+
+TEST(QuicProtocolTest, InsertMissingPacketsBetween) {
+  QuicAckFrame ack_frame;
+  InsertMissingPacketsBetween(&ack_frame, 4u, 10u);
+  EXPECT_EQ(6u, ack_frame.missing_packets.size());
+
+  QuicPacketSequenceNumber i = 4;
+  for (SequenceNumberSet::iterator it = ack_frame.missing_packets.begin();
+       it != ack_frame.missing_packets.end(); ++it, ++i) {
+    EXPECT_EQ(i, *it);
+  }
+}
+
+TEST(QuicProtocolTest, QuicVersionToQuicTag) {
+  // If you add a new version to the QuicVersion enum you will need to add a new
+  // case to QuicVersionToQuicTag, otherwise this test will fail.
+
+  // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+  // Any logs would indicate an unsupported version which we don't expect.
+  ScopedMockLog log(kDoNotCaptureLogsYet);
+  EXPECT_CALL(log, Log(_, _, _)).Times(0);
+  log.StartCapturingLogs();
+#endif
+
+  // Explicitly test a specific version.
+  EXPECT_EQ(MakeQuicTag('Q', '0', '1', '9'),
+            QuicVersionToQuicTag(QUIC_VERSION_19));
+
+  // Loop over all supported versions and make sure that we never hit the
+  // default case (i.e. all supported versions should be successfully converted
+  // to valid QuicTags).
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    QuicVersion version = kSupportedQuicVersions[i];
+    EXPECT_LT(0u, QuicVersionToQuicTag(version));
+  }
+}
+
+TEST(QuicProtocolTest, QuicVersionToQuicTagUnsupported) {
+  // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+  // TODO(rjshade): Change to DFATAL once we actually support multiple versions,
+  // and QuicConnectionTest::SendVersionNegotiationPacket can be changed to use
+  // mis-matched versions rather than relying on QUIC_VERSION_UNSUPPORTED.
+  ScopedMockLog log(kDoNotCaptureLogsYet);
+  EXPECT_CALL(log, Log(ERROR, _, "Unsupported QuicVersion: 0")).Times(1);
+  log.StartCapturingLogs();
+#endif
+
+  EXPECT_EQ(0u, QuicVersionToQuicTag(QUIC_VERSION_UNSUPPORTED));
+}
+
+TEST(QuicProtocolTest, QuicTagToQuicVersion) {
+  // If you add a new version to the QuicVersion enum you will need to add a new
+  // case to QuicTagToQuicVersion, otherwise this test will fail.
+
+  // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+  // Any logs would indicate an unsupported version which we don't expect.
+  ScopedMockLog log(kDoNotCaptureLogsYet);
+  EXPECT_CALL(log, Log(_, _, _)).Times(0);
+  log.StartCapturingLogs();
+#endif
+
+  // Explicitly test specific versions.
+  EXPECT_EQ(QUIC_VERSION_19,
+            QuicTagToQuicVersion(MakeQuicTag('Q', '0', '1', '9')));
+
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    QuicVersion version = kSupportedQuicVersions[i];
+
+    // Get the tag from the version (we can loop over QuicVersions easily).
+    QuicTag tag = QuicVersionToQuicTag(version);
+    EXPECT_LT(0u, tag);
+
+    // Now try converting back.
+    QuicVersion tag_to_quic_version = QuicTagToQuicVersion(tag);
+    EXPECT_EQ(version, tag_to_quic_version);
+    EXPECT_NE(QUIC_VERSION_UNSUPPORTED, tag_to_quic_version);
+  }
+}
+
+TEST(QuicProtocolTest, QuicTagToQuicVersionUnsupported) {
+  // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+  ScopedMockLog log(kDoNotCaptureLogsYet);
+#ifndef NDEBUG
+  EXPECT_CALL(log, Log(INFO, _, "Unsupported QuicTag version: FAKE")).Times(1);
+#endif
+  log.StartCapturingLogs();
+#endif
+
+  EXPECT_EQ(QUIC_VERSION_UNSUPPORTED,
+            QuicTagToQuicVersion(MakeQuicTag('F', 'A', 'K', 'E')));
+}
+
+TEST(QuicProtocolTest, QuicVersionToString) {
+  EXPECT_EQ("QUIC_VERSION_19", QuicVersionToString(QUIC_VERSION_19));
+  EXPECT_EQ("QUIC_VERSION_UNSUPPORTED",
+            QuicVersionToString(QUIC_VERSION_UNSUPPORTED));
+
+  QuicVersion single_version[] = {QUIC_VERSION_19};
+  QuicVersionVector versions_vector;
+  for (size_t i = 0; i < arraysize(single_version); ++i) {
+    versions_vector.push_back(single_version[i]);
+  }
+  EXPECT_EQ("QUIC_VERSION_19", QuicVersionVectorToString(versions_vector));
+
+  QuicVersion multiple_versions[] = {QUIC_VERSION_UNSUPPORTED, QUIC_VERSION_19};
+  versions_vector.clear();
+  for (size_t i = 0; i < arraysize(multiple_versions); ++i) {
+    versions_vector.push_back(multiple_versions[i]);
+  }
+  EXPECT_EQ("QUIC_VERSION_UNSUPPORTED,QUIC_VERSION_19",
+            QuicVersionVectorToString(versions_vector));
+
+  // Make sure that all supported versions are present in QuicVersionToString.
+  for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+    QuicVersion version = kSupportedQuicVersions[i];
+    EXPECT_NE("QUIC_VERSION_UNSUPPORTED", QuicVersionToString(version));
+  }
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_received_packet_manager.cc b/net/quic/quic_received_packet_manager.cc
new file mode 100644
index 0000000..255ce4d
--- /dev/null
+++ b/net/quic/quic_received_packet_manager.cc
@@ -0,0 +1,296 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_received_packet_manager.h"
+
+#include <limits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_connection_stats.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+using std::numeric_limits;
+
+namespace net {
+
+namespace {
+
+// The maximum number of packets to ack immediately after a missing packet for
+// fast retransmission to kick in at the sender.  This limit is created to
+// reduce the number of acks sent that have no benefit for fast retransmission.
+// Set to the number of nacks needed for fast retransmit plus one for protection
+// against an ack loss
+const size_t kMaxPacketsAfterNewMissing = 4;
+
+}
+
+QuicReceivedPacketManager::EntropyTracker::EntropyTracker()
+    :  packets_entropy_hash_(0),
+       first_gap_(1),
+       largest_observed_(0) {
+}
+
+QuicReceivedPacketManager::EntropyTracker::~EntropyTracker() {}
+
+QuicPacketEntropyHash QuicReceivedPacketManager::EntropyTracker::EntropyHash(
+    QuicPacketSequenceNumber sequence_number) const {
+  DCHECK_LE(sequence_number, largest_observed_);
+  if (sequence_number == largest_observed_) {
+    return packets_entropy_hash_;
+  }
+
+  DCHECK_GE(sequence_number, first_gap_);
+  DCHECK_EQ(first_gap_ + packets_entropy_.size() - 1, largest_observed_);
+  QuicPacketEntropyHash hash = packets_entropy_hash_;
+  ReceivedEntropyHashes::const_reverse_iterator it = packets_entropy_.rbegin();
+  for (QuicPacketSequenceNumber i = 0;
+           i < (largest_observed_ - sequence_number); ++i, ++it) {
+    hash ^= it->first;
+  }
+  return hash;
+}
+
+void QuicReceivedPacketManager::EntropyTracker::RecordPacketEntropyHash(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketEntropyHash entropy_hash) {
+  if (sequence_number < first_gap_) {
+    DVLOG(1) << "Ignoring received packet entropy for sequence_number:"
+             << sequence_number << " less than largest_peer_sequence_number:"
+             << first_gap_;
+    return;
+  }
+  // RecordPacketEntropyHash is only intended to be called once per packet.
+  DCHECK(sequence_number > largest_observed_ ||
+         !packets_entropy_[sequence_number - first_gap_].second);
+
+  packets_entropy_hash_ ^= entropy_hash;
+
+  // Optimize the typical case of no gaps.
+  if (sequence_number == largest_observed_ + 1 && packets_entropy_.empty()) {
+    ++first_gap_;
+    largest_observed_ = sequence_number;
+    return;
+  }
+  if (sequence_number > largest_observed_) {
+    for (QuicPacketSequenceNumber i = 0;
+         i < (sequence_number - largest_observed_ - 1); ++i) {
+      packets_entropy_.push_back(make_pair(0, false));
+    }
+    packets_entropy_.push_back(make_pair(entropy_hash, true));
+    largest_observed_ = sequence_number;
+  } else {
+    packets_entropy_[sequence_number - first_gap_] =
+        make_pair(entropy_hash, true);
+    AdvanceFirstGapAndGarbageCollectEntropyMap();
+  }
+
+  DVLOG(2) << "setting cumulative received entropy hash to: "
+           << static_cast<int>(packets_entropy_hash_)
+           << " updated with sequence number " << sequence_number
+           << " entropy hash: " << static_cast<int>(entropy_hash);
+}
+
+void QuicReceivedPacketManager::EntropyTracker::SetCumulativeEntropyUpTo(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketEntropyHash entropy_hash) {
+  DCHECK_LE(sequence_number, largest_observed_);
+  if (sequence_number < first_gap_) {
+    DVLOG(1) << "Ignoring set entropy at:" << sequence_number
+             << " less than first_gap_:" << first_gap_;
+    return;
+  }
+  while (first_gap_ < sequence_number) {
+    ++first_gap_;
+    if (!packets_entropy_.empty()) {
+      packets_entropy_.pop_front();
+    }
+  }
+  // Compute the current entropy by XORing in all entropies received including
+  // and since sequence_number.
+  packets_entropy_hash_ = entropy_hash;
+  for (ReceivedEntropyHashes::const_iterator it = packets_entropy_.begin();
+           it != packets_entropy_.end(); ++it) {
+    packets_entropy_hash_ ^= it->first;
+  }
+
+  // Garbage collect entries from the beginning of the map.
+  AdvanceFirstGapAndGarbageCollectEntropyMap();
+}
+
+void QuicReceivedPacketManager::EntropyTracker::
+AdvanceFirstGapAndGarbageCollectEntropyMap() {
+  while (!packets_entropy_.empty() && packets_entropy_.front().second) {
+    ++first_gap_;
+    packets_entropy_.pop_front();
+  }
+}
+
+QuicReceivedPacketManager::QuicReceivedPacketManager(
+    QuicConnectionStats* stats)
+    : peer_least_packet_awaiting_ack_(0),
+      time_largest_observed_(QuicTime::Zero()),
+      receive_algorithm_(ReceiveAlgorithmInterface::Create(kTCP)),
+      stats_(stats) {
+  ack_frame_.largest_observed = 0;
+  ack_frame_.entropy_hash = 0;
+}
+
+QuicReceivedPacketManager::~QuicReceivedPacketManager() {}
+
+void QuicReceivedPacketManager::RecordPacketReceived(
+    QuicByteCount bytes,
+    const QuicPacketHeader& header,
+    QuicTime receipt_time) {
+  QuicPacketSequenceNumber sequence_number = header.packet_sequence_number;
+  DCHECK(IsAwaitingPacket(sequence_number));
+
+  InsertMissingPacketsBetween(
+      &ack_frame_,
+      max(ack_frame_.largest_observed + 1, peer_least_packet_awaiting_ack_),
+      sequence_number);
+
+  if (ack_frame_.largest_observed > sequence_number) {
+    // We've gotten one of the out of order packets - remove it from our
+    // "missing packets" list.
+    DVLOG(1) << "Removing " << sequence_number << " from missing list";
+    ack_frame_.missing_packets.erase(sequence_number);
+
+    // Record how out of order stats.
+    ++stats_->packets_reordered;
+    uint32 sequence_gap = ack_frame_.largest_observed - sequence_number;
+    stats_->max_sequence_reordering =
+        max(stats_->max_sequence_reordering, sequence_gap);
+    uint32 reordering_time_us =
+        receipt_time.Subtract(time_largest_observed_).ToMicroseconds();
+    stats_->max_time_reordering_us = max(stats_->max_time_reordering_us,
+                                         reordering_time_us);
+  }
+  if (sequence_number > ack_frame_.largest_observed) {
+    ack_frame_.largest_observed = sequence_number;
+    time_largest_observed_ = receipt_time;
+  }
+  entropy_tracker_.RecordPacketEntropyHash(sequence_number,
+                                           header.entropy_hash);
+
+  receive_algorithm_->RecordIncomingPacket(
+      bytes, sequence_number, receipt_time);
+
+  received_packet_times_.push_back(
+      std::make_pair(sequence_number, receipt_time));
+
+  ack_frame_.revived_packets.erase(sequence_number);
+}
+
+void QuicReceivedPacketManager::RecordPacketRevived(
+    QuicPacketSequenceNumber sequence_number) {
+  LOG_IF(DFATAL, !IsAwaitingPacket(sequence_number));
+  ack_frame_.revived_packets.insert(sequence_number);
+}
+
+bool QuicReceivedPacketManager::IsMissing(
+    QuicPacketSequenceNumber sequence_number) {
+  return ContainsKey(ack_frame_.missing_packets, sequence_number);
+}
+
+bool QuicReceivedPacketManager::IsAwaitingPacket(
+    QuicPacketSequenceNumber sequence_number) {
+  return ::net::IsAwaitingPacket(ack_frame_, sequence_number);
+}
+
+namespace {
+struct isTooLarge {
+  explicit isTooLarge(QuicPacketSequenceNumber n) : largest_observed_(n) {}
+  QuicPacketSequenceNumber largest_observed_;
+
+  // Return true if the packet in p is too different from largest_observed_
+  // to express.
+  bool operator() (
+      const std::pair<QuicPacketSequenceNumber, QuicTime>& p) const {
+    return largest_observed_ - p.first >= numeric_limits<uint8>::max();
+  }
+};
+}  // namespace
+
+void QuicReceivedPacketManager::UpdateReceivedPacketInfo(
+    QuicAckFrame* ack_frame, QuicTime approximate_now) {
+  *ack_frame = ack_frame_;
+  ack_frame->entropy_hash = EntropyHash(ack_frame_.largest_observed);
+
+  if (time_largest_observed_ == QuicTime::Zero()) {
+    // We have received no packets.
+    ack_frame->delta_time_largest_observed = QuicTime::Delta::Infinite();
+    return;
+  }
+
+  if (approximate_now < time_largest_observed_) {
+    // Approximate now may well be "in the past".
+    ack_frame->delta_time_largest_observed = QuicTime::Delta::Zero();
+    return;
+  }
+
+  ack_frame->delta_time_largest_observed =
+      approximate_now.Subtract(time_largest_observed_);
+
+  // Remove all packets that are too far from largest_observed to express.
+  received_packet_times_.remove_if(isTooLarge(ack_frame_.largest_observed));
+
+  ack_frame->received_packet_times = received_packet_times_;
+  received_packet_times_.clear();
+}
+
+bool QuicReceivedPacketManager::GenerateCongestionFeedback(
+    QuicCongestionFeedbackFrame* feedback) {
+  return receive_algorithm_->GenerateCongestionFeedback(feedback);
+}
+
+QuicPacketEntropyHash QuicReceivedPacketManager::EntropyHash(
+    QuicPacketSequenceNumber sequence_number) const {
+  return entropy_tracker_.EntropyHash(sequence_number);
+}
+
+bool QuicReceivedPacketManager::DontWaitForPacketsBefore(
+    QuicPacketSequenceNumber least_unacked) {
+  ack_frame_.revived_packets.erase(
+      ack_frame_.revived_packets.begin(),
+      ack_frame_.revived_packets.lower_bound(least_unacked));
+  size_t missing_packets_count = ack_frame_.missing_packets.size();
+  ack_frame_.missing_packets.erase(
+      ack_frame_.missing_packets.begin(),
+      ack_frame_.missing_packets.lower_bound(least_unacked));
+  return missing_packets_count != ack_frame_.missing_packets.size();
+}
+
+void QuicReceivedPacketManager::UpdatePacketInformationSentByPeer(
+    const QuicStopWaitingFrame& stop_waiting) {
+  // ValidateAck() should fail if peer_least_packet_awaiting_ack_ shrinks.
+  DCHECK_LE(peer_least_packet_awaiting_ack_, stop_waiting.least_unacked);
+  if (stop_waiting.least_unacked > peer_least_packet_awaiting_ack_) {
+    bool missed_packets = DontWaitForPacketsBefore(stop_waiting.least_unacked);
+    if (missed_packets) {
+      DVLOG(1) << "Updating entropy hashed since we missed packets";
+      // There were some missing packets that we won't ever get now. Recalculate
+      // the received entropy hash.
+      entropy_tracker_.SetCumulativeEntropyUpTo(stop_waiting.least_unacked,
+                                                stop_waiting.entropy_hash);
+    }
+    peer_least_packet_awaiting_ack_ = stop_waiting.least_unacked;
+  }
+  DCHECK(ack_frame_.missing_packets.empty() ||
+         *ack_frame_.missing_packets.begin() >=
+             peer_least_packet_awaiting_ack_);
+}
+
+bool QuicReceivedPacketManager::HasNewMissingPackets() {
+  return !ack_frame_.missing_packets.empty() &&
+      (ack_frame_.largest_observed -
+       *ack_frame_.missing_packets.rbegin()) <= kMaxPacketsAfterNewMissing;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_received_packet_manager.h b/net/quic/quic_received_packet_manager.h
new file mode 100644
index 0000000..1231891
--- /dev/null
+++ b/net/quic/quic_received_packet_manager.h
@@ -0,0 +1,182 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Manages the packet entropy calculation for both sent and received packets
+// for a connection.
+
+#ifndef NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
+#define NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
+
+#include <deque>
+
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class EntropyTrackerPeer;
+class QuicConnectionPeer;
+class QuicReceivedPacketManagerPeer;
+}  // namespace test
+
+struct QuicConnectionStats;
+
+// Records all received packets by a connection and tracks their entropy.
+// Also calculates the correct entropy for the framer when it truncates an ack
+// frame being serialized.
+class NET_EXPORT_PRIVATE QuicReceivedPacketManager :
+    public QuicReceivedEntropyHashCalculatorInterface {
+ public:
+  class NET_EXPORT_PRIVATE EntropyTracker {
+   public:
+    EntropyTracker();
+    ~EntropyTracker();
+
+    // Compute the XOR of the entropy of all received packets up to
+    // and including sequence_number.
+    // Requires that either:
+    //   sequence_number == largest_observed_
+    // or:
+    //   sequence_number > first_gap_ &&
+    //   sequence_number < largest_observed_ &&
+    //   sequence_number in packets_entropy_
+    QuicPacketEntropyHash EntropyHash(
+        QuicPacketSequenceNumber sequence_number) const;
+
+    // Record the received entropy hash against |sequence_number|.
+    // Performs garbage collection to advance first_gap_ if
+    // sequence_number == first_gap_.
+    void RecordPacketEntropyHash(QuicPacketSequenceNumber sequence_number,
+                                 QuicPacketEntropyHash entropy_hash);
+
+    // Sets the entropy hash up to but not including a sequence number based
+    // on the hash provided by a StopWaiting frame.  Clears older packet
+    // entropy entries and performs garbage collection up to the first gap.
+    void SetCumulativeEntropyUpTo(QuicPacketSequenceNumber sequence_number,
+                                  QuicPacketEntropyHash entropy_hash);
+
+   private:
+    friend class test::EntropyTrackerPeer;
+
+    // A deque indexed by sequence number storing the packet's hash and whether
+    // a hash was recorded for that sequence number.
+    typedef std::deque<std::pair<QuicPacketEntropyHash, bool> >
+        ReceivedEntropyHashes;
+
+    // Recomputes first_gap_ and removes packets_entropy_ entries that are no
+    // longer needed to compute EntropyHash.
+    void AdvanceFirstGapAndGarbageCollectEntropyMap();
+
+    // Map of received sequence numbers to their corresponding entropy.
+    // Stores an entry for every received packet whose sequence_number is larger
+    // than first_gap_.  Packets without the entropy bit set have an entropy
+    // value of 0.
+    ReceivedEntropyHashes packets_entropy_;
+
+    // Cumulative hash of entropy of all received packets.
+    QuicPacketEntropyHash packets_entropy_hash_;
+
+    // Sequence number of the first packet that we do not know the entropy of.
+    // If there are no gaps in the received packet sequence,
+    // packets_entropy_ will be empty and first_gap_ will be equal to
+    // 'largest_observed_ + 1' since that's the first packet for which
+    // entropy is unknown.  If there are gaps, packets_entropy_ will
+    // contain entries for all received packets with sequence_number >
+    // first_gap_.
+    QuicPacketSequenceNumber first_gap_;
+
+    // Sequence number of the largest observed packet.
+    QuicPacketSequenceNumber largest_observed_;
+
+    DISALLOW_COPY_AND_ASSIGN(EntropyTracker);
+  };
+
+  explicit QuicReceivedPacketManager(QuicConnectionStats* stats);
+  virtual ~QuicReceivedPacketManager();
+
+  // Updates the internal state concerning which packets have been received.
+  // bytes: the packet size in bytes including Quic Headers.
+  // header: the packet header.
+  // timestamp: the arrival time of the packet.
+  void RecordPacketReceived(QuicByteCount bytes,
+                            const QuicPacketHeader& header,
+                            QuicTime receipt_time);
+
+  void RecordPacketRevived(QuicPacketSequenceNumber sequence_number);
+
+  // Checks whether |sequence_number| is missing and less than largest observed.
+  bool IsMissing(QuicPacketSequenceNumber sequence_number);
+
+  // Checks if we're still waiting for the packet with |sequence_number|.
+  bool IsAwaitingPacket(QuicPacketSequenceNumber sequence_number);
+
+  // Update the |ack_frame| for an outgoing ack.
+  void UpdateReceivedPacketInfo(QuicAckFrame* ack_frame,
+                                QuicTime approximate_now);
+
+  // Should be called before sending an ACK packet, to decide if we need
+  // to attach a QuicCongestionFeedbackFrame block.
+  // Returns false if no QuicCongestionFeedbackFrame block is needed.
+  // Otherwise fills in feedback and returns true.
+  virtual bool GenerateCongestionFeedback(
+      QuicCongestionFeedbackFrame* feedback);
+
+  // QuicReceivedEntropyHashCalculatorInterface
+  // Called by QuicFramer, when the outgoing ack gets truncated, to recalculate
+  // the received entropy hash for the truncated ack frame.
+  virtual QuicPacketEntropyHash EntropyHash(
+      QuicPacketSequenceNumber sequence_number) const OVERRIDE;
+
+  // Updates internal state based on |stop_waiting|.
+  void UpdatePacketInformationSentByPeer(
+      const QuicStopWaitingFrame& stop_waiting);
+
+  // Returns true when there are new missing packets to be reported within 3
+  // packets of the largest observed.
+  bool HasNewMissingPackets();
+
+  QuicPacketSequenceNumber peer_least_packet_awaiting_ack() {
+    return peer_least_packet_awaiting_ack_;
+  }
+
+ private:
+  friend class test::QuicConnectionPeer;
+  friend class test::QuicReceivedPacketManagerPeer;
+
+  // Deletes all missing packets before least unacked. The connection won't
+  // process any packets with sequence number before |least_unacked| that it
+  // received after this call. Returns true if there were missing packets before
+  // |least_unacked| unacked, false otherwise.
+  bool DontWaitForPacketsBefore(QuicPacketSequenceNumber least_unacked);
+
+  // Tracks entropy hashes of received packets.
+  EntropyTracker entropy_tracker_;
+
+  // Least sequence number of the the packet sent by the peer for which it
+  // hasn't received an ack.
+  QuicPacketSequenceNumber peer_least_packet_awaiting_ack_;
+
+  // Received packet information used to produce acks.
+  QuicAckFrame ack_frame_;
+
+  // The time we received the largest_observed sequence number, or zero if
+  // no sequence numbers have been received since UpdateReceivedPacketInfo.
+  // Needed for calculating delta_time_largest_observed.
+  QuicTime time_largest_observed_;
+
+  scoped_ptr<ReceiveAlgorithmInterface> receive_algorithm_;
+
+  QuicConnectionStats* stats_;
+
+  PacketTimeList received_packet_times_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicReceivedPacketManager);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
diff --git a/net/quic/quic_received_packet_manager_test.cc b/net/quic/quic_received_packet_manager_test.cc
new file mode 100644
index 0000000..9e8c128
--- /dev/null
+++ b/net/quic/quic_received_packet_manager_test.cc
@@ -0,0 +1,365 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_received_packet_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/test_tools/quic_received_packet_manager_peer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class EntropyTrackerPeer {
+ public:
+  static QuicPacketSequenceNumber first_gap(
+      const QuicReceivedPacketManager::EntropyTracker& tracker) {
+    return tracker.first_gap_;
+  }
+  static QuicPacketSequenceNumber largest_observed(
+      const QuicReceivedPacketManager::EntropyTracker& tracker) {
+    return tracker.largest_observed_;
+  }
+  static int packets_entropy_size(
+      const QuicReceivedPacketManager::EntropyTracker& tracker) {
+    return tracker.packets_entropy_.size();
+  }
+  static bool IsTrackingPacket(
+      const QuicReceivedPacketManager::EntropyTracker& tracker,
+      QuicPacketSequenceNumber sequence_number) {
+    return sequence_number >= tracker.first_gap_ &&
+        sequence_number <
+            (tracker.first_gap_ + tracker.packets_entropy_.size()) &&
+        tracker.packets_entropy_[sequence_number - tracker.first_gap_].second;
+  }
+};
+
+namespace {
+
+// Entropy of individual packets is not tracked if there are no gaps.
+TEST(EntropyTrackerTest, NoGaps) {
+  QuicReceivedPacketManager::EntropyTracker tracker;
+
+  tracker.RecordPacketEntropyHash(1, 23);
+  tracker.RecordPacketEntropyHash(2, 42);
+
+  EXPECT_EQ(23 ^ 42, tracker.EntropyHash(2));
+  EXPECT_EQ(3u, EntropyTrackerPeer::first_gap(tracker));
+
+  EXPECT_EQ(2u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(0, EntropyTrackerPeer::packets_entropy_size(tracker));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 1));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 2));
+}
+
+// Entropy of individual packets is tracked as long as there are gaps.
+// Filling the first gap results in entropy getting garbage collected.
+TEST(EntropyTrackerTest, FillGaps) {
+  QuicReceivedPacketManager::EntropyTracker tracker;
+
+  tracker.RecordPacketEntropyHash(2, 5);
+  tracker.RecordPacketEntropyHash(5, 17);
+  tracker.RecordPacketEntropyHash(6, 23);
+  tracker.RecordPacketEntropyHash(9, 42);
+
+  EXPECT_EQ(1u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(9, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(5, tracker.EntropyHash(2));
+  EXPECT_EQ(5 ^ 17, tracker.EntropyHash(5));
+  EXPECT_EQ(5 ^ 17 ^ 23, tracker.EntropyHash(6));
+  EXPECT_EQ(5 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 1));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 2));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 5));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 6));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 9));
+
+  // Fill the gap at 1.
+  tracker.RecordPacketEntropyHash(1, 2);
+
+  EXPECT_EQ(3u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(7, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(2 ^ 5 ^ 17, tracker.EntropyHash(5));
+  EXPECT_EQ(2 ^ 5 ^ 17 ^ 23, tracker.EntropyHash(6));
+  EXPECT_EQ(2 ^ 5 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 1));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 2));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 5));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 6));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 9));
+
+  // Fill the gap at 4.
+  tracker.RecordPacketEntropyHash(4, 2);
+
+  EXPECT_EQ(3u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(7, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(5, tracker.EntropyHash(4));
+  EXPECT_EQ(5 ^ 17, tracker.EntropyHash(5));
+  EXPECT_EQ(5 ^ 17 ^ 23, tracker.EntropyHash(6));
+  EXPECT_EQ(5 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 3));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 4));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 5));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 6));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 9));
+
+  // Fill the gap at 3.  Entropy for packets 3 to 6 are forgotten.
+  tracker.RecordPacketEntropyHash(3, 2);
+
+  EXPECT_EQ(7u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(3, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(2 ^ 5 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 3));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 4));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 5));
+  EXPECT_FALSE(EntropyTrackerPeer::IsTrackingPacket(tracker, 6));
+  EXPECT_TRUE(EntropyTrackerPeer::IsTrackingPacket(tracker, 9));
+
+  // Fill in the rest.
+  tracker.RecordPacketEntropyHash(7, 2);
+  tracker.RecordPacketEntropyHash(8, 2);
+
+  EXPECT_EQ(10u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(0, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(2 ^ 5 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+}
+
+TEST(EntropyTrackerTest, SetCumulativeEntropyUpTo) {
+  QuicReceivedPacketManager::EntropyTracker tracker;
+
+  tracker.RecordPacketEntropyHash(2, 5);
+  tracker.RecordPacketEntropyHash(5, 17);
+  tracker.RecordPacketEntropyHash(6, 23);
+  tracker.RecordPacketEntropyHash(9, 42);
+
+  EXPECT_EQ(1u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(9, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  // Inform the tracker about value of the hash at a gap.
+  tracker.SetCumulativeEntropyUpTo(3, 7);
+  EXPECT_EQ(3u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(7, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(7 ^ 17, tracker.EntropyHash(5));
+  EXPECT_EQ(7 ^ 17 ^ 23, tracker.EntropyHash(6));
+  EXPECT_EQ(7 ^ 17 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  // Inform the tracker about value of the hash at a known location.
+  tracker.SetCumulativeEntropyUpTo(6, 1);
+  EXPECT_EQ(7u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(3, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(1 ^ 23 ^ 42, tracker.EntropyHash(9));
+
+  // Inform the tracker about value of the hash at the last location.
+  tracker.SetCumulativeEntropyUpTo(9, 21);
+  EXPECT_EQ(10u, EntropyTrackerPeer::first_gap(tracker));
+  EXPECT_EQ(9u, EntropyTrackerPeer::largest_observed(tracker));
+  EXPECT_EQ(0, EntropyTrackerPeer::packets_entropy_size(tracker));
+
+  EXPECT_EQ(42 ^ 21, tracker.EntropyHash(9));
+}
+
+class QuicReceivedPacketManagerTest : public ::testing::Test {
+ protected:
+  QuicReceivedPacketManagerTest() : received_manager_(&stats_) {}
+
+  void RecordPacketReceipt(QuicPacketSequenceNumber sequence_number,
+                           QuicPacketEntropyHash entropy_hash) {
+    RecordPacketReceipt(sequence_number, entropy_hash, QuicTime::Zero());
+  }
+
+  void RecordPacketReceipt(QuicPacketSequenceNumber sequence_number,
+                           QuicPacketEntropyHash entropy_hash,
+                           QuicTime receipt_time) {
+    QuicPacketHeader header;
+    header.packet_sequence_number = sequence_number;
+    header.entropy_hash = entropy_hash;
+    received_manager_.RecordPacketReceived(0u, header, receipt_time);
+  }
+
+  void RecordPacketRevived(QuicPacketSequenceNumber sequence_number) {
+    received_manager_.RecordPacketRevived(sequence_number);
+  }
+
+  QuicConnectionStats stats_;
+  QuicReceivedPacketManager received_manager_;
+};
+
+TEST_F(QuicReceivedPacketManagerTest, ReceivedPacketEntropyHash) {
+  vector<pair<QuicPacketSequenceNumber, QuicPacketEntropyHash> > entropies;
+  entropies.push_back(make_pair(1, 12));
+  entropies.push_back(make_pair(7, 1));
+  entropies.push_back(make_pair(2, 33));
+  entropies.push_back(make_pair(5, 3));
+  entropies.push_back(make_pair(8, 34));
+
+  for (size_t i = 0; i < entropies.size(); ++i) {
+    RecordPacketReceipt(entropies[i].first, entropies[i].second);
+  }
+
+  sort(entropies.begin(), entropies.end());
+
+  QuicPacketEntropyHash hash = 0;
+  size_t index = 0;
+  for (size_t i = 1; i <= (*entropies.rbegin()).first; ++i) {
+    if (entropies[index].first == i) {
+      hash ^= entropies[index].second;
+      ++index;
+    }
+    if (i < 3) continue;
+    EXPECT_EQ(hash, received_manager_.EntropyHash(i));
+  }
+  // Reorder by 5 when 2 is received after 7.
+  EXPECT_EQ(5u, stats_.max_sequence_reordering);
+  EXPECT_EQ(0u, stats_.max_time_reordering_us);
+  EXPECT_EQ(2u, stats_.packets_reordered);
+}
+
+TEST_F(QuicReceivedPacketManagerTest, EntropyHashBelowLeastObserved) {
+  EXPECT_EQ(0, received_manager_.EntropyHash(0));
+  RecordPacketReceipt(4, 5);
+  EXPECT_EQ(0, received_manager_.EntropyHash(3));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, EntropyHashAboveLargestObserved) {
+  EXPECT_EQ(0, received_manager_.EntropyHash(0));
+  RecordPacketReceipt(4, 5);
+  EXPECT_EQ(0, received_manager_.EntropyHash(3));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, SetCumulativeEntropyUpTo) {
+  vector<pair<QuicPacketSequenceNumber, QuicPacketEntropyHash> > entropies;
+  entropies.push_back(make_pair(1, 12));
+  entropies.push_back(make_pair(2, 1));
+  entropies.push_back(make_pair(3, 33));
+  entropies.push_back(make_pair(4, 3));
+  entropies.push_back(make_pair(6, 34));
+  entropies.push_back(make_pair(7, 29));
+
+  QuicPacketEntropyHash entropy_hash = 0;
+  for (size_t i = 0; i < entropies.size(); ++i) {
+    RecordPacketReceipt(entropies[i].first, entropies[i].second);
+    entropy_hash ^= entropies[i].second;
+  }
+  EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(7));
+
+  // Now set the entropy hash up to 5 to be 100.
+  entropy_hash ^= 100;
+  for (size_t i = 0; i < 4; ++i) {
+    entropy_hash ^= entropies[i].second;
+  }
+  QuicReceivedPacketManagerPeer::SetCumulativeEntropyUpTo(
+      &received_manager_, 5, 100);
+  EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(7));
+
+  QuicReceivedPacketManagerPeer::SetCumulativeEntropyUpTo(
+      &received_manager_, 1, 50);
+  EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(7));
+
+  // No reordering.
+  EXPECT_EQ(0u, stats_.max_sequence_reordering);
+  EXPECT_EQ(0u, stats_.max_time_reordering_us);
+  EXPECT_EQ(0u, stats_.packets_reordered);
+}
+
+TEST_F(QuicReceivedPacketManagerTest, DontWaitForPacketsBefore) {
+  QuicPacketHeader header;
+  header.packet_sequence_number = 2u;
+  received_manager_.RecordPacketReceived(0u, header, QuicTime::Zero());
+  header.packet_sequence_number = 7u;
+  received_manager_.RecordPacketReceived(0u, header, QuicTime::Zero());
+  EXPECT_TRUE(received_manager_.IsAwaitingPacket(3u));
+  EXPECT_TRUE(received_manager_.IsAwaitingPacket(6u));
+  EXPECT_TRUE(QuicReceivedPacketManagerPeer::DontWaitForPacketsBefore(
+      &received_manager_, 4));
+  EXPECT_FALSE(received_manager_.IsAwaitingPacket(3u));
+  EXPECT_TRUE(received_manager_.IsAwaitingPacket(6u));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, UpdateReceivedPacketInfo) {
+  QuicPacketHeader header;
+  header.packet_sequence_number = 2u;
+  QuicTime two_ms = QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(2));
+  received_manager_.RecordPacketReceived(0u, header, two_ms);
+
+  QuicAckFrame ack;
+  received_manager_.UpdateReceivedPacketInfo(&ack, QuicTime::Zero());
+  // When UpdateReceivedPacketInfo with a time earlier than the time of the
+  // largest observed packet, make sure that the delta is 0, not negative.
+  EXPECT_EQ(QuicTime::Delta::Zero(), ack.delta_time_largest_observed);
+
+  QuicTime four_ms = QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(4));
+  received_manager_.UpdateReceivedPacketInfo(&ack, four_ms);
+  // When UpdateReceivedPacketInfo after not having received a new packet,
+  // the delta should still be accurate.
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(2),
+            ack.delta_time_largest_observed);
+}
+
+TEST_F(QuicReceivedPacketManagerTest, UpdateReceivedConnectionStats) {
+  RecordPacketReceipt(1, 0);
+  RecordPacketReceipt(6, 0);
+  RecordPacketReceipt(
+      2, 0, QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(1)));
+
+  EXPECT_EQ(4u, stats_.max_sequence_reordering);
+  EXPECT_EQ(1000u, stats_.max_time_reordering_us);
+  EXPECT_EQ(1u, stats_.packets_reordered);
+}
+
+TEST_F(QuicReceivedPacketManagerTest, RevivedPacket) {
+  RecordPacketReceipt(1, 0);
+  RecordPacketReceipt(3, 0);
+  RecordPacketRevived(2);
+
+  QuicAckFrame ack;
+  received_manager_.UpdateReceivedPacketInfo(&ack, QuicTime::Zero());
+  EXPECT_EQ(1u, ack.missing_packets.size());
+  EXPECT_EQ(2u, *ack.missing_packets.begin());
+  EXPECT_EQ(1u, ack.revived_packets.size());
+  EXPECT_EQ(2u, *ack.missing_packets.begin());
+}
+
+TEST_F(QuicReceivedPacketManagerTest, PacketRevivedThenReceived) {
+  RecordPacketReceipt(1, 0);
+  RecordPacketReceipt(3, 0);
+  RecordPacketRevived(2);
+  RecordPacketReceipt(2, 0);
+
+  QuicAckFrame ack;
+  received_manager_.UpdateReceivedPacketInfo(&ack, QuicTime::Zero());
+  EXPECT_TRUE(ack.missing_packets.empty());
+  EXPECT_TRUE(ack.revived_packets.empty());
+}
+
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_reliable_client_stream.cc b/net/quic/quic_reliable_client_stream.cc
new file mode 100644
index 0000000..b1a82d1
--- /dev/null
+++ b/net/quic/quic_reliable_client_stream.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_reliable_client_stream.h"
+
+#include "base/callback_helpers.h"
+#include "net/base/net_errors.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_write_blocked_list.h"
+
+namespace net {
+
+QuicReliableClientStream::QuicReliableClientStream(QuicStreamId id,
+                                                   QuicSession* session,
+                                                   const BoundNetLog& net_log)
+    : QuicDataStream(id, session),
+      net_log_(net_log),
+      delegate_(nullptr) {
+}
+
+QuicReliableClientStream::~QuicReliableClientStream() {
+  if (delegate_)
+    delegate_->OnClose(connection_error());
+}
+
+uint32 QuicReliableClientStream::ProcessData(const char* data,
+                                             uint32 data_len) {
+  // TODO(rch): buffer data if we don't have a delegate.
+  if (!delegate_) {
+    DLOG(ERROR) << "Missing delegate";
+    Reset(QUIC_STREAM_CANCELLED);
+    return 0;
+  }
+
+  int rv = delegate_->OnDataReceived(data, data_len);
+  if (rv != OK) {
+    DLOG(ERROR) << "Delegate refused data, rv: " << rv;
+    Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+    return 0;
+  }
+  return data_len;
+}
+
+void QuicReliableClientStream::OnFinRead() {
+  if (delegate_) {
+    delegate_->OnClose(connection_error());
+    delegate_ = nullptr;
+  }
+  ReliableQuicStream::OnFinRead();
+}
+
+void QuicReliableClientStream::OnCanWrite() {
+  ReliableQuicStream::OnCanWrite();
+
+  if (!HasBufferedData() && !callback_.is_null()) {
+    base::ResetAndReturn(&callback_).Run(OK);
+  }
+}
+
+QuicPriority QuicReliableClientStream::EffectivePriority() const {
+  if (delegate_ && delegate_->HasSendHeadersComplete()) {
+    return QuicDataStream::EffectivePriority();
+  }
+  return QuicWriteBlockedList::kHighestPriority;
+}
+
+int QuicReliableClientStream::WriteStreamData(
+    base::StringPiece data,
+    bool fin,
+    const CompletionCallback& callback) {
+  // We should not have data buffered.
+  DCHECK(!HasBufferedData());
+  // Writes the data, or buffers it.
+  WriteOrBufferData(data, fin, nullptr);
+  if (!HasBufferedData()) {
+    return OK;
+  }
+
+  callback_ = callback;
+  return ERR_IO_PENDING;
+}
+
+void QuicReliableClientStream::SetDelegate(
+    QuicReliableClientStream::Delegate* delegate) {
+  DCHECK((!delegate_ && delegate) || (delegate_ && !delegate));
+  delegate_ = delegate;
+}
+
+void QuicReliableClientStream::OnError(int error) {
+  if (delegate_) {
+    QuicReliableClientStream::Delegate* delegate = delegate_;
+    delegate_ = nullptr;
+    delegate->OnError(error);
+  }
+}
+
+bool QuicReliableClientStream::CanWrite(const CompletionCallback& callback) {
+  bool can_write =  session()->connection()->CanWrite(HAS_RETRANSMITTABLE_DATA);
+  if (!can_write) {
+    session()->MarkWriteBlocked(id(), EffectivePriority());
+    DCHECK(callback_.is_null());
+    callback_ = callback;
+  }
+  return can_write;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_reliable_client_stream.h b/net/quic/quic_reliable_client_stream.h
new file mode 100644
index 0000000..2d5b818
--- /dev/null
+++ b/net/quic/quic_reliable_client_stream.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// NOTE: This code is not shared between Google and Chrome.
+
+#ifndef NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+#define NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_stream.h"
+#include "net/quic/quic_data_stream.h"
+
+namespace net {
+
+class QuicClientSession;
+
+// A client-initiated ReliableQuicStream.  Instances of this class
+// are owned by the QuicClientSession which created them.
+class NET_EXPORT_PRIVATE QuicReliableClientStream : public QuicDataStream {
+ public:
+  // Delegate handles protocol specific behavior of a quic stream.
+  class NET_EXPORT_PRIVATE Delegate {
+   public:
+    Delegate() {}
+
+    // Called when data is received.
+    // Returns network error code. OK when it successfully receives data.
+    virtual int OnDataReceived(const char* data, int length) = 0;
+
+    // Called when the stream is closed by the peer.
+    virtual void OnClose(QuicErrorCode error) = 0;
+
+    // Called when the stream is closed because of an error.
+    virtual void OnError(int error) = 0;
+
+    // Returns true if sending of headers has completed.
+    virtual bool HasSendHeadersComplete() = 0;
+
+   protected:
+    virtual ~Delegate() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Delegate);
+  };
+
+  QuicReliableClientStream(QuicStreamId id,
+                           QuicSession* session,
+                           const BoundNetLog& net_log);
+
+  virtual ~QuicReliableClientStream();
+
+  // QuicDataStream
+  virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+  virtual void OnFinRead() OVERRIDE;
+  virtual void OnCanWrite() OVERRIDE;
+  virtual QuicPriority EffectivePriority() const OVERRIDE;
+
+  // While the server's set_priority shouldn't be called externally, the creator
+  // of client-side streams should be able to set the priority.
+  using QuicDataStream::set_priority;
+
+  int WriteStreamData(base::StringPiece data,
+                      bool fin,
+                      const CompletionCallback& callback);
+  // Set new |delegate|. |delegate| must not be NULL.
+  // If this stream has already received data, OnDataReceived() will be
+  // called on the delegate.
+  void SetDelegate(Delegate* delegate);
+  Delegate* GetDelegate() { return delegate_; }
+  void OnError(int error);
+
+  // Returns true if the stream can possible write data.  (The socket may
+  // turn out to be write blocked, of course).  If the stream can not write,
+  // this method returns false, and |callback| will be invoked when
+  // it becomes writable.
+  bool CanWrite(const CompletionCallback& callback);
+
+  const BoundNetLog& net_log() const { return net_log_; }
+
+  using QuicDataStream::HasBufferedData;
+
+ private:
+  BoundNetLog net_log_;
+  Delegate* delegate_;
+
+  CompletionCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicReliableClientStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
diff --git a/net/quic/quic_reliable_client_stream_test.cc b/net/quic/quic_reliable_client_stream_test.cc
new file mode 100644
index 0000000..caa697a
--- /dev/null
+++ b/net/quic/quic_reliable_client_stream_test.cc
@@ -0,0 +1,173 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_reliable_client_stream.h"
+
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::AnyNumber;
+using testing::Return;
+using testing::StrEq;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const QuicConnectionId kStreamId = 3;
+
+class MockDelegate : public QuicReliableClientStream::Delegate {
+ public:
+  MockDelegate() {}
+
+  MOCK_METHOD0(OnSendData, int());
+  MOCK_METHOD2(OnSendDataComplete, int(int, bool*));
+  MOCK_METHOD2(OnDataReceived, int(const char*, int));
+  MOCK_METHOD1(OnClose, void(QuicErrorCode));
+  MOCK_METHOD1(OnError, void(int));
+  MOCK_METHOD0(HasSendHeadersComplete, bool());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+class QuicReliableClientStreamTest
+    : public ::testing::TestWithParam<QuicVersion> {
+ public:
+  QuicReliableClientStreamTest()
+      : session_(new MockConnection(false, SupportedVersions(GetParam()))) {
+    stream_ = new QuicReliableClientStream(kStreamId, &session_, BoundNetLog());
+    session_.ActivateStream(stream_);
+    stream_->SetDelegate(&delegate_);
+  }
+
+  void InitializeHeaders() {
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "https";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+  }
+
+  testing::StrictMock<MockDelegate> delegate_;
+  MockSession session_;
+  QuicReliableClientStream* stream_;
+  QuicCryptoClientConfig crypto_config_;
+  SpdyHeaderBlock headers_;
+};
+
+INSTANTIATE_TEST_CASE_P(Version, QuicReliableClientStreamTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicReliableClientStreamTest, OnFinRead) {
+  InitializeHeaders();
+  string uncompressed_headers =
+      SpdyUtils::SerializeUncompressedHeaders(headers_);
+  EXPECT_CALL(delegate_, OnDataReceived(StrEq(uncompressed_headers.data()),
+                                        uncompressed_headers.size()));
+  QuicStreamOffset offset = 0;
+  stream_->OnStreamHeaders(uncompressed_headers);
+  stream_->OnStreamHeadersComplete(false, uncompressed_headers.length());
+
+  IOVector iov;
+  QuicStreamFrame frame2(kStreamId, true, offset, iov);
+  EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+  stream_->OnStreamFrame(frame2);
+}
+
+TEST_P(QuicReliableClientStreamTest, ProcessData) {
+  const char data[] = "hello world!";
+  EXPECT_CALL(delegate_, OnDataReceived(StrEq(data), arraysize(data)));
+  EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+  EXPECT_EQ(arraysize(data), stream_->ProcessData(data, arraysize(data)));
+}
+
+TEST_P(QuicReliableClientStreamTest, ProcessDataWithError) {
+  const char data[] = "hello world!";
+  EXPECT_CALL(delegate_,
+              OnDataReceived(StrEq(data),
+                             arraysize(data))).WillOnce(Return(ERR_UNEXPECTED));
+  EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+
+  EXPECT_EQ(0u, stream_->ProcessData(data, arraysize(data)));
+}
+
+TEST_P(QuicReliableClientStreamTest, OnError) {
+  EXPECT_CALL(delegate_, OnError(ERR_INTERNET_DISCONNECTED));
+
+  stream_->OnError(ERR_INTERNET_DISCONNECTED);
+  EXPECT_FALSE(stream_->GetDelegate());
+}
+
+TEST_P(QuicReliableClientStreamTest, WriteStreamData) {
+  EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+  const char kData1[] = "hello world";
+  const size_t kDataLen = arraysize(kData1);
+
+  // All data written.
+  EXPECT_CALL(session_, WritevData(stream_->id(), _,  _, _, _, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen, true)));
+  TestCompletionCallback callback;
+  EXPECT_EQ(OK, stream_->WriteStreamData(base::StringPiece(kData1, kDataLen),
+                                         true, callback.callback()));
+}
+
+TEST_P(QuicReliableClientStreamTest, WriteStreamDataAsync) {
+  EXPECT_CALL(delegate_, HasSendHeadersComplete()).Times(AnyNumber());
+  EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+  const char kData1[] = "hello world";
+  const size_t kDataLen = arraysize(kData1);
+
+  // No data written.
+  EXPECT_CALL(session_, WritevData(stream_->id(),  _, _, _, _, _)).WillOnce(
+      Return(QuicConsumedData(0, false)));
+  TestCompletionCallback callback;
+  EXPECT_EQ(ERR_IO_PENDING,
+            stream_->WriteStreamData(base::StringPiece(kData1, kDataLen),
+                                     true, callback.callback()));
+  ASSERT_FALSE(callback.have_result());
+
+  // All data written.
+  EXPECT_CALL(session_, WritevData(stream_->id(),  _, _, _, _, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen, true)));
+  stream_->OnCanWrite();
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_sent_entropy_manager.cc b/net/quic/quic_sent_entropy_manager.cc
new file mode 100644
index 0000000..206a9ff
--- /dev/null
+++ b/net/quic/quic_sent_entropy_manager.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sent_entropy_manager.h"
+
+#include "base/logging.h"
+#include "net/base/linked_hash_map.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+
+namespace net {
+
+QuicSentEntropyManager::QuicSentEntropyManager() : map_offset_(1) {}
+
+QuicSentEntropyManager::~QuicSentEntropyManager() {}
+
+QuicPacketEntropyHash QuicSentEntropyManager::GetPacketEntropy(
+    QuicPacketSequenceNumber sequence_number) const {
+  return packets_entropy_[sequence_number - map_offset_];
+}
+
+QuicPacketSequenceNumber
+QuicSentEntropyManager::GetLargestPacketWithEntropy() const {
+  return map_offset_ + packets_entropy_.size() - 1;
+}
+
+QuicPacketSequenceNumber
+QuicSentEntropyManager::GetSmallestPacketWithEntropy() const {
+  return map_offset_;
+}
+
+void QuicSentEntropyManager::UpdateCumulativeEntropy(
+    QuicPacketSequenceNumber sequence_number,
+    CumulativeEntropy* cumulative) const {
+  while (cumulative->sequence_number < sequence_number) {
+    ++cumulative->sequence_number;
+    cumulative->entropy ^= GetPacketEntropy(cumulative->sequence_number);
+  }
+}
+
+void QuicSentEntropyManager::RecordPacketEntropyHash(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketEntropyHash entropy_hash) {
+  if (!packets_entropy_.empty()) {
+    // Ensure packets always are recorded in order.
+    // Every packet's entropy is recorded, even if it's not sent, so there
+    // are not sequence number gaps.
+    DCHECK_EQ(GetLargestPacketWithEntropy() + 1, sequence_number);
+  }
+  packets_entropy_.push_back(entropy_hash);
+  DVLOG(2) << "Recorded sequence number " << sequence_number
+           << " with entropy hash: " << static_cast<int>(entropy_hash);
+}
+
+QuicPacketEntropyHash QuicSentEntropyManager::GetCumulativeEntropy(
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK_LE(last_cumulative_entropy_.sequence_number, sequence_number);
+  DCHECK_GE(GetLargestPacketWithEntropy(), sequence_number);
+  // First the entropy for largest_observed sequence number should be updated.
+  UpdateCumulativeEntropy(sequence_number, &last_cumulative_entropy_);
+  return last_cumulative_entropy_.entropy;
+}
+
+bool QuicSentEntropyManager::IsValidEntropy(
+    QuicPacketSequenceNumber largest_observed,
+    const SequenceNumberSet& missing_packets,
+    QuicPacketEntropyHash entropy_hash) {
+  DCHECK_GE(largest_observed, last_valid_entropy_.sequence_number);
+  // Ensure the largest and smallest sequence numbers are in range.
+  if (largest_observed > GetLargestPacketWithEntropy()) {
+    return false;
+  }
+  if (!missing_packets.empty() &&
+      *missing_packets.begin() < GetSmallestPacketWithEntropy()) {
+    return false;
+  }
+  // First the entropy for largest_observed sequence number should be updated.
+  UpdateCumulativeEntropy(largest_observed, &last_valid_entropy_);
+
+  // Now XOR out all the missing entropies.
+  QuicPacketEntropyHash expected_entropy_hash = last_valid_entropy_.entropy;
+  for (SequenceNumberSet::const_iterator it = missing_packets.begin();
+       it != missing_packets.end(); ++it) {
+    expected_entropy_hash ^= GetPacketEntropy(*it);
+  }
+  DLOG_IF(WARNING, entropy_hash != expected_entropy_hash)
+      << "Invalid entropy hash: " << static_cast<int>(entropy_hash)
+      << " expected entropy hash: " << static_cast<int>(expected_entropy_hash);
+  return entropy_hash == expected_entropy_hash;
+}
+
+void QuicSentEntropyManager::ClearEntropyBefore(
+    QuicPacketSequenceNumber sequence_number) {
+  // Don't discard entropy before updating the cumulative entropy used to
+  // calculate EntropyHash and IsValidEntropy.
+  if (last_cumulative_entropy_.sequence_number < sequence_number) {
+    UpdateCumulativeEntropy(sequence_number, &last_cumulative_entropy_);
+  }
+  if (last_valid_entropy_.sequence_number < sequence_number) {
+    UpdateCumulativeEntropy(sequence_number, &last_valid_entropy_);
+  }
+  while (map_offset_ < sequence_number) {
+    packets_entropy_.pop_front();
+    ++map_offset_;
+  }
+  DVLOG(2) << "Cleared entropy before: " << sequence_number;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_sent_entropy_manager.h b/net/quic/quic_sent_entropy_manager.h
new file mode 100644
index 0000000..9173d95
--- /dev/null
+++ b/net/quic/quic_sent_entropy_manager.h
@@ -0,0 +1,89 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Manages the packet entropy calculation for both sent and received packets
+// for a connection.
+
+#ifndef NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
+#define NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
+
+#include <deque>
+
+#include "net/base/linked_hash_map.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicConnectionPeer;
+}  // namespace test
+
+// Records all sent packets by a connection to track the cumulative entropy of
+// sent packets.  It is used by the connection to validate an ack
+// frame sent by the peer as a preventive measure against the optimistic ack
+// attack.
+class NET_EXPORT_PRIVATE QuicSentEntropyManager {
+ public:
+  QuicSentEntropyManager();
+  virtual ~QuicSentEntropyManager();
+
+  // Record |entropy_hash| for sent packet corresponding to |sequence_number|.
+  void RecordPacketEntropyHash(QuicPacketSequenceNumber sequence_number,
+                               QuicPacketEntropyHash entropy_hash);
+
+  // Retrieves the cumulative entropy up to |sequence_number|.
+  // Must always be called with a monotonically increasing |sequence_number|.
+  QuicPacketEntropyHash GetCumulativeEntropy(
+      QuicPacketSequenceNumber sequence_number);
+
+  // Returns true if |entropy_hash| matches the expected sent entropy hash
+  // up to |largest_observed| removing sequence numbers from |missing_packets|.
+  // Must always be called with a monotonically increasing |largest_observed|.
+  bool IsValidEntropy(QuicPacketSequenceNumber largest_observed,
+                      const SequenceNumberSet& missing_packets,
+                      QuicPacketEntropyHash entropy_hash);
+
+  // Removes unnecessary entries before |sequence_number|.
+  void ClearEntropyBefore(QuicPacketSequenceNumber sequence_number);
+
+ private:
+  friend class test::QuicConnectionPeer;
+
+  typedef std::deque<QuicPacketEntropyHash> SentEntropyMap;
+
+  struct CumulativeEntropy {
+    CumulativeEntropy() : sequence_number(0), entropy(0) {}
+
+    QuicPacketSequenceNumber sequence_number;
+    QuicPacketEntropyHash entropy;
+  };
+
+  // Convenience methods to get the largest and smallest packets with entropies.
+  QuicPacketSequenceNumber GetLargestPacketWithEntropy() const;
+  QuicPacketSequenceNumber GetSmallestPacketWithEntropy() const;
+  // Convenience method to get the entropy hash for |sequence_number|.
+  QuicPacketEntropyHash GetPacketEntropy(
+      QuicPacketSequenceNumber sequence_number) const;
+
+  // Update the cumulative entropy to |sequence_number|.
+  void UpdateCumulativeEntropy(QuicPacketSequenceNumber sequence_number,
+                               CumulativeEntropy* cumulative) const;
+
+  // Maps sequence numbers to the sent entropy hash for the sequence number.
+  SentEntropyMap packets_entropy_;
+  QuicPacketSequenceNumber map_offset_;
+
+  // Cache the cumulative entropy for IsValidEntropy.
+  CumulativeEntropy last_valid_entropy_;
+
+  // Cache the cumulative entropy for the sequence number used by EntropyHash.
+  CumulativeEntropy last_cumulative_entropy_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSentEntropyManager);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
diff --git a/net/quic/quic_sent_entropy_manager_test.cc b/net/quic/quic_sent_entropy_manager_test.cc
new file mode 100644
index 0000000..78bdcce
--- /dev/null
+++ b/net/quic/quic_sent_entropy_manager_test.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sent_entropy_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicSentEntropyManagerTest : public ::testing::Test {
+ protected:
+  QuicSentEntropyManager entropy_manager_;
+};
+
+TEST_F(QuicSentEntropyManagerTest, SentEntropyHash) {
+  EXPECT_EQ(0, entropy_manager_.GetCumulativeEntropy(0));
+
+  QuicPacketEntropyHash entropies[4] = {12, 1, 33, 3};
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    entropy_manager_.RecordPacketEntropyHash(i + 1, entropies[i]);
+  }
+
+  QuicPacketEntropyHash hash = 0;
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    hash ^= entropies[i];
+    EXPECT_EQ(hash, entropy_manager_.GetCumulativeEntropy(i + 1));
+  }
+}
+
+TEST_F(QuicSentEntropyManagerTest, IsValidEntropy) {
+  QuicPacketEntropyHash entropies[10] =
+      {12, 1, 33, 3, 32, 100, 28, 42, 22, 255};
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    entropy_manager_.RecordPacketEntropyHash(i + 1, entropies[i]);
+  }
+
+  SequenceNumberSet missing_packets;
+  missing_packets.insert(1);
+  missing_packets.insert(4);
+  missing_packets.insert(7);
+  missing_packets.insert(8);
+
+  QuicPacketEntropyHash entropy_hash = 0;
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    if (missing_packets.find(i + 1) == missing_packets.end()) {
+      entropy_hash ^= entropies[i];
+    }
+  }
+
+  EXPECT_TRUE(entropy_manager_.IsValidEntropy(10, missing_packets,
+                                              entropy_hash));
+}
+
+TEST_F(QuicSentEntropyManagerTest, ClearEntropiesBefore) {
+  QuicPacketEntropyHash entropies[10] =
+      {12, 1, 33, 3, 32, 100, 28, 42, 22, 255};
+
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    entropy_manager_.RecordPacketEntropyHash(i + 1, entropies[i]);
+  }
+
+  // Discard the first 5 entropies and ensure IsValidEntropy and EntropyHash
+  // still return correct results.
+  entropy_manager_.ClearEntropyBefore(5);
+
+  SequenceNumberSet missing_packets;
+  missing_packets.insert(7);
+  missing_packets.insert(8);
+
+  QuicPacketEntropyHash entropy_hash = 0;
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    if (missing_packets.find(i + 1) == missing_packets.end()) {
+      entropy_hash ^= entropies[i];
+    }
+  }
+  EXPECT_TRUE(entropy_manager_.IsValidEntropy(10, missing_packets,
+                                              entropy_hash));
+
+  entropy_hash = 0;
+  for (size_t i = 0; i < arraysize(entropies); ++i) {
+    entropy_hash ^= entropies[i];
+  }
+  EXPECT_EQ(entropy_hash, entropy_manager_.GetCumulativeEntropy(10));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_sent_packet_manager.cc b/net/quic/quic_sent_packet_manager.cc
new file mode 100644
index 0000000..a062827
--- /dev/null
+++ b/net/quic/quic_sent_packet_manager.cc
@@ -0,0 +1,889 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sent_packet_manager.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/pacing_sender.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_ack_notifier_manager.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_utils_chromium.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+
+namespace net {
+
+// The length of the recent min rtt window in seconds. Windowing is disabled for
+// values less than or equal to 0.
+int32 FLAGS_quic_recent_min_rtt_window_s = 60;
+
+namespace {
+static const int kDefaultRetransmissionTimeMs = 500;
+// TCP RFC calls for 1 second RTO however Linux differs from this default and
+// define the minimum RTO to 200ms, we will use the same until we have data to
+// support a higher or lower value.
+static const int kMinRetransmissionTimeMs = 200;
+static const int kMaxRetransmissionTimeMs = 60000;
+static const size_t kMaxRetransmissions = 10;
+
+// Only exponentially back off the handshake timer 5 times due to a timeout.
+static const size_t kMaxHandshakeRetransmissionBackoffs = 5;
+static const size_t kMinHandshakeTimeoutMs = 10;
+
+// Sends up to two tail loss probes before firing an RTO,
+// per draft RFC draft-dukkipati-tcpm-tcp-loss-probe.
+static const size_t kDefaultMaxTailLossProbes = 2;
+static const int64 kMinTailLossProbeTimeoutMs = 10;
+
+// Number of samples before we force a new recent min rtt to be captured.
+static const size_t kNumMinRttSamplesAfterQuiescence = 2;
+
+// Number of unpaced packets to send after quiescence.
+static const size_t kInitialUnpacedBurst = 10;
+
+bool HasCryptoHandshake(const TransmissionInfo& transmission_info) {
+  if (transmission_info.retransmittable_frames == nullptr) {
+    return false;
+  }
+  return transmission_info.retransmittable_frames->HasCryptoHandshake() ==
+      IS_HANDSHAKE;
+}
+
+}  // namespace
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+QuicSentPacketManager::QuicSentPacketManager(
+    bool is_server,
+    const QuicClock* clock,
+    QuicConnectionStats* stats,
+    CongestionControlType congestion_control_type,
+    LossDetectionType loss_type)
+    : unacked_packets_(),
+      is_server_(is_server),
+      clock_(clock),
+      stats_(stats),
+      debug_delegate_(nullptr),
+      network_change_visitor_(nullptr),
+      send_algorithm_(SendAlgorithmInterface::Create(clock,
+                                                     &rtt_stats_,
+                                                     congestion_control_type,
+                                                     stats)),
+      loss_algorithm_(LossDetectionInterface::Create(loss_type)),
+      least_packet_awaited_by_peer_(1),
+      first_rto_transmission_(0),
+      consecutive_rto_count_(0),
+      consecutive_tlp_count_(0),
+      consecutive_crypto_retransmission_count_(0),
+      pending_timer_transmission_count_(0),
+      max_tail_loss_probes_(kDefaultMaxTailLossProbes),
+      using_pacing_(false),
+      handshake_confirmed_(false) {
+}
+
+QuicSentPacketManager::~QuicSentPacketManager() {
+}
+
+void QuicSentPacketManager::SetFromConfig(const QuicConfig& config) {
+  if (config.HasReceivedInitialRoundTripTimeUs() &&
+      config.ReceivedInitialRoundTripTimeUs() > 0) {
+    rtt_stats_.set_initial_rtt_us(min(kMaxInitialRoundTripTimeUs,
+                                      config.ReceivedInitialRoundTripTimeUs()));
+  } else if (config.HasInitialRoundTripTimeUsToSend()) {
+    rtt_stats_.set_initial_rtt_us(
+        min(kMaxInitialRoundTripTimeUs,
+            config.GetInitialRoundTripTimeUsToSend()));
+  }
+  // TODO(ianswett): BBR is currently a server only feature.
+  if (config.HasReceivedConnectionOptions() &&
+      ContainsQuicTag(config.ReceivedConnectionOptions(), kTBBR)) {
+    if (FLAGS_quic_recent_min_rtt_window_s > 0) {
+      rtt_stats_.set_recent_min_rtt_window(
+          QuicTime::Delta::FromSeconds(FLAGS_quic_recent_min_rtt_window_s));
+    }
+    send_algorithm_.reset(
+        SendAlgorithmInterface::Create(clock_, &rtt_stats_, kBBR, stats_));
+  }
+  if (config.HasReceivedConnectionOptions() &&
+      ContainsQuicTag(config.ReceivedConnectionOptions(), kRENO)) {
+    send_algorithm_.reset(
+        SendAlgorithmInterface::Create(clock_, &rtt_stats_, kReno, stats_));
+  }
+  if (HasClientSentConnectionOption(config, kPACE)) {
+    EnablePacing();
+  }
+  if (HasClientSentConnectionOption(config, k1CON)) {
+    send_algorithm_->SetNumEmulatedConnections(1);
+  }
+  if (config.HasReceivedConnectionOptions() &&
+      ContainsQuicTag(config.ReceivedConnectionOptions(), kTIME)) {
+    loss_algorithm_.reset(LossDetectionInterface::Create(kTime));
+  }
+  send_algorithm_->SetFromConfig(config, is_server_);
+
+  if (network_change_visitor_ != nullptr) {
+    network_change_visitor_->OnCongestionWindowChange(GetCongestionWindow());
+  }
+}
+
+bool QuicSentPacketManager::HasClientSentConnectionOption(
+    const QuicConfig& config, QuicTag tag) const {
+  if (is_server_) {
+    if (config.HasReceivedConnectionOptions() &&
+        ContainsQuicTag(config.ReceivedConnectionOptions(), tag)) {
+      return true;
+    }
+  } else if (config.HasSendConnectionOptions() &&
+             ContainsQuicTag(config.SendConnectionOptions(), tag)) {
+    return true;
+  }
+  return false;
+}
+
+void QuicSentPacketManager::OnRetransmittedPacket(
+    QuicPacketSequenceNumber old_sequence_number,
+    QuicPacketSequenceNumber new_sequence_number) {
+  TransmissionType transmission_type;
+  PendingRetransmissionMap::iterator it =
+      pending_retransmissions_.find(old_sequence_number);
+  if (it != pending_retransmissions_.end()) {
+    transmission_type = it->second;
+    pending_retransmissions_.erase(it);
+  } else {
+    DLOG(DFATAL) << "Expected sequence number to be in "
+        "pending_retransmissions_.  sequence_number: " << old_sequence_number;
+    transmission_type = NOT_RETRANSMISSION;
+  }
+
+  // A notifier may be waiting to hear about ACKs for the original sequence
+  // number. Inform them that the sequence number has changed.
+  ack_notifier_manager_.UpdateSequenceNumber(old_sequence_number,
+                                             new_sequence_number);
+
+  unacked_packets_.OnRetransmittedPacket(old_sequence_number,
+                                         new_sequence_number,
+                                         transmission_type);
+}
+
+void QuicSentPacketManager::OnIncomingAck(const QuicAckFrame& ack_frame,
+                                          QuicTime ack_receive_time) {
+  QuicByteCount bytes_in_flight = unacked_packets_.bytes_in_flight();
+
+  UpdatePacketInformationReceivedByPeer(ack_frame);
+  // We rely on delta_time_largest_observed to compute an RTT estimate, so
+  // we only update rtt when the largest observed gets acked.
+  bool largest_observed_acked = MaybeUpdateRTT(ack_frame, ack_receive_time);
+  DCHECK_GE(ack_frame.largest_observed, unacked_packets_.largest_observed());
+  unacked_packets_.IncreaseLargestObserved(ack_frame.largest_observed);
+
+  HandleAckForSentPackets(ack_frame);
+  InvokeLossDetection(ack_receive_time);
+  MaybeInvokeCongestionEvent(largest_observed_acked, bytes_in_flight);
+  unacked_packets_.RemoveObsoletePackets();
+
+  sustained_bandwidth_recorder_.RecordEstimate(
+      send_algorithm_->InRecovery(),
+      send_algorithm_->InSlowStart(),
+      send_algorithm_->BandwidthEstimate(),
+      ack_receive_time,
+      clock_->WallNow(),
+      rtt_stats_.SmoothedRtt());
+
+  // If we have received a truncated ack, then we need to clear out some
+  // previous transmissions to allow the peer to actually ACK new packets.
+  if (ack_frame.is_truncated) {
+    unacked_packets_.ClearAllPreviousRetransmissions();
+  }
+
+  // Anytime we are making forward progress and have a new RTT estimate, reset
+  // the backoff counters.
+  if (largest_observed_acked) {
+    // Reset all retransmit counters any time a new packet is acked.
+    consecutive_rto_count_ = 0;
+    consecutive_tlp_count_ = 0;
+    consecutive_crypto_retransmission_count_ = 0;
+  }
+
+  if (debug_delegate_ != nullptr) {
+    debug_delegate_->OnIncomingAck(ack_frame,
+                                   ack_receive_time,
+                                   unacked_packets_.largest_observed(),
+                                   largest_observed_acked,
+                                   GetLeastUnacked());
+  }
+}
+
+void QuicSentPacketManager::UpdatePacketInformationReceivedByPeer(
+    const QuicAckFrame& ack_frame) {
+  if (ack_frame.missing_packets.empty()) {
+    least_packet_awaited_by_peer_ = ack_frame.largest_observed + 1;
+  } else {
+    least_packet_awaited_by_peer_ = *(ack_frame.missing_packets.begin());
+  }
+}
+
+void QuicSentPacketManager::MaybeInvokeCongestionEvent(
+    bool rtt_updated, QuicByteCount bytes_in_flight) {
+  if (!rtt_updated && packets_acked_.empty() && packets_lost_.empty()) {
+    return;
+  }
+  send_algorithm_->OnCongestionEvent(rtt_updated, bytes_in_flight,
+                                     packets_acked_, packets_lost_);
+  packets_acked_.clear();
+  packets_lost_.clear();
+  if (network_change_visitor_ != nullptr) {
+    network_change_visitor_->OnCongestionWindowChange(GetCongestionWindow());
+  }
+}
+
+void QuicSentPacketManager::HandleAckForSentPackets(
+    const QuicAckFrame& ack_frame) {
+  // Go through the packets we have not received an ack for and see if this
+  // incoming_ack shows they've been seen by the peer.
+  QuicTime::Delta delta_largest_observed =
+      ack_frame.delta_time_largest_observed;
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    if (sequence_number > ack_frame.largest_observed) {
+      // These packets are still in flight.
+      break;
+    }
+
+    if (ContainsKey(ack_frame.missing_packets, sequence_number)) {
+      // Don't continue to increase the nack count for packets not in flight.
+      if (!it->in_flight) {
+        continue;
+      }
+      // Consider it multiple nacks when there is a gap between the missing
+      // packet and the largest observed, since the purpose of a nack
+      // threshold is to tolerate re-ordering.  This handles both StretchAcks
+      // and Forward Acks.
+      // The nack count only increases when the largest observed increases.
+      size_t min_nacks = ack_frame.largest_observed - sequence_number;
+      // Truncated acks can nack the largest observed, so use a min of 1.
+      if (min_nacks == 0) {
+        min_nacks = 1;
+      }
+      unacked_packets_.NackPacket(sequence_number, min_nacks);
+      continue;
+    }
+    // Packet was acked, so remove it from our unacked packet list.
+    DVLOG(1) << ENDPOINT << "Got an ack for packet " << sequence_number;
+    // If data is associated with the most recent transmission of this
+    // packet, then inform the caller.
+    if (it->in_flight) {
+      packets_acked_.push_back(make_pair(sequence_number, *it));
+    }
+    MarkPacketHandled(sequence_number, *it, delta_largest_observed);
+  }
+
+  // Discard any retransmittable frames associated with revived packets.
+  for (SequenceNumberSet::const_iterator revived_it =
+           ack_frame.revived_packets.begin();
+       revived_it != ack_frame.revived_packets.end(); ++revived_it) {
+    MarkPacketRevived(*revived_it, delta_largest_observed);
+  }
+}
+
+bool QuicSentPacketManager::HasRetransmittableFrames(
+    QuicPacketSequenceNumber sequence_number) const {
+  return unacked_packets_.HasRetransmittableFrames(sequence_number);
+}
+
+void QuicSentPacketManager::RetransmitUnackedPackets(
+    TransmissionType retransmission_type) {
+  DCHECK(retransmission_type == ALL_UNACKED_RETRANSMISSION ||
+         retransmission_type == ALL_INITIAL_RETRANSMISSION);
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    const RetransmittableFrames* frames = it->retransmittable_frames;
+    if (frames != nullptr &&
+        (retransmission_type == ALL_UNACKED_RETRANSMISSION ||
+         frames->encryption_level() == ENCRYPTION_INITIAL)) {
+      MarkForRetransmission(sequence_number, retransmission_type);
+    }
+  }
+}
+
+void QuicSentPacketManager::NeuterUnencryptedPackets() {
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    const RetransmittableFrames* frames = it->retransmittable_frames;
+    if (frames != nullptr && frames->encryption_level() == ENCRYPTION_NONE) {
+      // Once you're forward secure, no unencrypted packets will be sent, crypto
+      // or otherwise. Unencrypted packets are neutered and abandoned, to ensure
+      // they are not retransmitted or considered lost from a congestion control
+      // perspective.
+      pending_retransmissions_.erase(sequence_number);
+      unacked_packets_.RemoveFromInFlight(sequence_number);
+      unacked_packets_.RemoveRetransmittability(sequence_number);
+    }
+  }
+}
+
+void QuicSentPacketManager::MarkForRetransmission(
+    QuicPacketSequenceNumber sequence_number,
+    TransmissionType transmission_type) {
+  const TransmissionInfo& transmission_info =
+      unacked_packets_.GetTransmissionInfo(sequence_number);
+  LOG_IF(DFATAL, transmission_info.retransmittable_frames == nullptr);
+  if (transmission_type != TLP_RETRANSMISSION) {
+    unacked_packets_.RemoveFromInFlight(sequence_number);
+  }
+  // TODO(ianswett): Currently the RTO can fire while there are pending NACK
+  // retransmissions for the same data, which is not ideal.
+  if (ContainsKey(pending_retransmissions_, sequence_number)) {
+    return;
+  }
+
+  pending_retransmissions_[sequence_number] = transmission_type;
+}
+
+void QuicSentPacketManager::RecordSpuriousRetransmissions(
+    const SequenceNumberList& all_transmissions,
+    QuicPacketSequenceNumber acked_sequence_number) {
+  if (acked_sequence_number < first_rto_transmission_) {
+    // Cancel all pending RTO transmissions and restore their in flight status.
+    // Replace SRTT with latest_rtt and increase the variance to prevent
+    // a spurious RTO from happening again.
+    rtt_stats_.ExpireSmoothedMetrics();
+    for (PendingRetransmissionMap::const_iterator it =
+             pending_retransmissions_.begin();
+         it != pending_retransmissions_.end(); ++it) {
+      DCHECK_EQ(it->second, RTO_RETRANSMISSION);
+      unacked_packets_.RestoreInFlight(it->first);
+    }
+    pending_retransmissions_.clear();
+    send_algorithm_->RevertRetransmissionTimeout();
+    first_rto_transmission_ = 0;
+    ++stats_->spurious_rto_count;
+  }
+  for (SequenceNumberList::const_reverse_iterator it =
+           all_transmissions.rbegin();
+       it != all_transmissions.rend() && *it > acked_sequence_number; ++it) {
+    const TransmissionInfo& retransmit_info =
+        unacked_packets_.GetTransmissionInfo(*it);
+
+    stats_->bytes_spuriously_retransmitted += retransmit_info.bytes_sent;
+    ++stats_->packets_spuriously_retransmitted;
+    if (debug_delegate_ != nullptr) {
+      debug_delegate_->OnSpuriousPacketRetransmition(
+          retransmit_info.transmission_type,
+          retransmit_info.bytes_sent);
+    }
+  }
+}
+
+bool QuicSentPacketManager::HasPendingRetransmissions() const {
+  return !pending_retransmissions_.empty();
+}
+
+QuicSentPacketManager::PendingRetransmission
+    QuicSentPacketManager::NextPendingRetransmission() {
+  DCHECK(!pending_retransmissions_.empty());
+  QuicPacketSequenceNumber sequence_number =
+      pending_retransmissions_.begin()->first;
+  TransmissionType transmission_type = pending_retransmissions_.begin()->second;
+  if (unacked_packets_.HasPendingCryptoPackets()) {
+    // Ensure crypto packets are retransmitted before other packets.
+    PendingRetransmissionMap::const_iterator it =
+        pending_retransmissions_.begin();
+    do {
+      if (HasCryptoHandshake(unacked_packets_.GetTransmissionInfo(it->first))) {
+        sequence_number = it->first;
+        transmission_type = it->second;
+        break;
+      }
+      ++it;
+    } while (it != pending_retransmissions_.end());
+  }
+  DCHECK(unacked_packets_.IsUnacked(sequence_number)) << sequence_number;
+  const TransmissionInfo& transmission_info =
+      unacked_packets_.GetTransmissionInfo(sequence_number);
+  DCHECK(transmission_info.retransmittable_frames);
+
+  return PendingRetransmission(sequence_number,
+                               transmission_type,
+                               *transmission_info.retransmittable_frames,
+                               transmission_info.sequence_number_length);
+}
+
+void QuicSentPacketManager::MarkPacketRevived(
+    QuicPacketSequenceNumber sequence_number,
+    QuicTime::Delta delta_largest_observed) {
+  if (!unacked_packets_.IsUnacked(sequence_number)) {
+    return;
+  }
+
+  const TransmissionInfo& transmission_info =
+      unacked_packets_.GetTransmissionInfo(sequence_number);
+  QuicPacketSequenceNumber newest_transmission =
+      transmission_info.all_transmissions == nullptr
+          ? sequence_number
+          : *transmission_info.all_transmissions->rbegin();
+  // This packet has been revived at the receiver. If we were going to
+  // retransmit it, do not retransmit it anymore.
+  pending_retransmissions_.erase(newest_transmission);
+
+  // The AckNotifierManager needs to be notified for revived packets,
+  // since it indicates the packet arrived from the appliction's perspective.
+  if (transmission_info.retransmittable_frames) {
+    ack_notifier_manager_.OnPacketAcked(
+        newest_transmission, delta_largest_observed);
+  }
+
+  unacked_packets_.RemoveRetransmittability(sequence_number);
+}
+
+void QuicSentPacketManager::MarkPacketHandled(
+    QuicPacketSequenceNumber sequence_number,
+    const TransmissionInfo& info,
+    QuicTime::Delta delta_largest_observed) {
+  QuicPacketSequenceNumber newest_transmission =
+      info.all_transmissions == nullptr ? sequence_number
+      : *info.all_transmissions->rbegin();
+  // Remove the most recent packet, if it is pending retransmission.
+  pending_retransmissions_.erase(newest_transmission);
+
+  // The AckNotifierManager needs to be notified about the most recent
+  // transmission, since that's the one only one it tracks.
+  ack_notifier_manager_.OnPacketAcked(newest_transmission,
+                                      delta_largest_observed);
+  if (newest_transmission != sequence_number) {
+    RecordSpuriousRetransmissions(*info.all_transmissions, sequence_number);
+    // Remove the most recent packet from flight if it's a crypto handshake
+    // packet, since they won't be acked now that one has been processed.
+    // Other crypto handshake packets won't be in flight, only the newest
+    // transmission of a crypto packet is in flight at once.
+    // TODO(ianswett): Instead of handling all crypto packets special,
+    // only handle nullptr encrypted packets in a special way.
+    if (HasCryptoHandshake(
+        unacked_packets_.GetTransmissionInfo(newest_transmission))) {
+      unacked_packets_.RemoveFromInFlight(newest_transmission);
+    }
+  }
+
+  unacked_packets_.RemoveFromInFlight(sequence_number);
+  unacked_packets_.RemoveRetransmittability(sequence_number);
+}
+
+bool QuicSentPacketManager::IsUnacked(
+    QuicPacketSequenceNumber sequence_number) const {
+  return unacked_packets_.IsUnacked(sequence_number);
+}
+
+bool QuicSentPacketManager::HasUnackedPackets() const {
+  return unacked_packets_.HasUnackedPackets();
+}
+
+QuicPacketSequenceNumber
+QuicSentPacketManager::GetLeastUnacked() const {
+  return unacked_packets_.GetLeastUnacked();
+}
+
+bool QuicSentPacketManager::OnPacketSent(
+    SerializedPacket* serialized_packet,
+    QuicPacketSequenceNumber original_sequence_number,
+    QuicTime sent_time,
+    QuicByteCount bytes,
+    TransmissionType transmission_type,
+    HasRetransmittableData has_retransmittable_data) {
+  QuicPacketSequenceNumber sequence_number = serialized_packet->sequence_number;
+  DCHECK_LT(0u, sequence_number);
+  DCHECK(!unacked_packets_.IsUnacked(sequence_number));
+  LOG_IF(DFATAL, bytes == 0) << "Cannot send empty packets.";
+  if (debug_delegate_ != nullptr) {
+    debug_delegate_->OnSentPacket(*serialized_packet,
+                                  original_sequence_number,
+                                  sent_time,
+                                  bytes,
+                                  transmission_type);
+  }
+
+  if (original_sequence_number == 0) {
+    if (serialized_packet->retransmittable_frames) {
+      ack_notifier_manager_.OnSerializedPacket(*serialized_packet);
+    }
+    unacked_packets_.AddPacket(*serialized_packet);
+    serialized_packet->retransmittable_frames = nullptr;
+  } else {
+    OnRetransmittedPacket(original_sequence_number, sequence_number);
+  }
+
+  if (pending_timer_transmission_count_ > 0) {
+    --pending_timer_transmission_count_;
+  }
+
+  if (unacked_packets_.bytes_in_flight() == 0) {
+    // TODO(ianswett): Consider being less aggressive to force a new
+    // recent_min_rtt, likely by not discarding a relatively new sample.
+    DVLOG(1) << "Sampling a new recent min rtt within 2 samples. currently:"
+             << rtt_stats_.recent_min_rtt().ToMilliseconds() << "ms";
+    rtt_stats_.SampleNewRecentMinRtt(kNumMinRttSamplesAfterQuiescence);
+  }
+
+  // Only track packets as in flight that the send algorithm wants us to track.
+  const bool in_flight =
+      send_algorithm_->OnPacketSent(sent_time,
+                                    unacked_packets_.bytes_in_flight(),
+                                    sequence_number,
+                                    bytes,
+                                    has_retransmittable_data);
+  unacked_packets_.SetSent(sequence_number, sent_time, bytes, in_flight);
+
+  // Reset the retransmission timer anytime a pending packet is sent.
+  return in_flight;
+}
+
+void QuicSentPacketManager::OnRetransmissionTimeout() {
+  DCHECK(unacked_packets_.HasInFlightPackets());
+  DCHECK_EQ(0u, pending_timer_transmission_count_);
+  // Handshake retransmission, timer based loss detection, TLP, and RTO are
+  // implemented with a single alarm. The handshake alarm is set when the
+  // handshake has not completed, the loss alarm is set when the loss detection
+  // algorithm says to, and the TLP and  RTO alarms are set after that.
+  // The TLP alarm is always set to run for under an RTO.
+  switch (GetRetransmissionMode()) {
+    case HANDSHAKE_MODE:
+      ++stats_->crypto_retransmit_count;
+      RetransmitCryptoPackets();
+      return;
+    case LOSS_MODE: {
+      ++stats_->loss_timeout_count;
+      QuicByteCount bytes_in_flight = unacked_packets_.bytes_in_flight();
+      InvokeLossDetection(clock_->Now());
+      MaybeInvokeCongestionEvent(false, bytes_in_flight);
+      return;
+    }
+    case TLP_MODE:
+      // If no tail loss probe can be sent, because there are no retransmittable
+      // packets, execute a conventional RTO to abandon old packets.
+      ++stats_->tlp_count;
+      ++consecutive_tlp_count_;
+      pending_timer_transmission_count_ = 1;
+      // TLPs prefer sending new data instead of retransmitting data, so
+      // give the connection a chance to write before completing the TLP.
+      return;
+    case RTO_MODE:
+      ++stats_->rto_count;
+      RetransmitAllPackets();
+      return;
+  }
+}
+
+void QuicSentPacketManager::RetransmitCryptoPackets() {
+  DCHECK_EQ(HANDSHAKE_MODE, GetRetransmissionMode());
+  // TODO(ianswett): Typical TCP implementations only retransmit 5 times.
+  consecutive_crypto_retransmission_count_ =
+      min(kMaxHandshakeRetransmissionBackoffs,
+          consecutive_crypto_retransmission_count_ + 1);
+  bool packet_retransmitted = false;
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    // Only retransmit frames which are in flight, and therefore have been sent.
+    if (!it->in_flight || it->retransmittable_frames == nullptr ||
+        it->retransmittable_frames->HasCryptoHandshake() != IS_HANDSHAKE) {
+      continue;
+    }
+    packet_retransmitted = true;
+    MarkForRetransmission(sequence_number, HANDSHAKE_RETRANSMISSION);
+    ++pending_timer_transmission_count_;
+  }
+  DCHECK(packet_retransmitted) << "No crypto packets found to retransmit.";
+}
+
+bool QuicSentPacketManager::MaybeRetransmitTailLossProbe() {
+  if (pending_timer_transmission_count_ == 0) {
+    return false;
+  }
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    // Only retransmit frames which are in flight, and therefore have been sent.
+    if (!it->in_flight || it->retransmittable_frames == nullptr) {
+      continue;
+    }
+    if (!handshake_confirmed_) {
+      DCHECK_NE(IS_HANDSHAKE, it->retransmittable_frames->HasCryptoHandshake());
+    }
+    MarkForRetransmission(sequence_number, TLP_RETRANSMISSION);
+    return true;
+  }
+  DLOG(FATAL)
+    << "No retransmittable packets, so RetransmitOldestPacket failed.";
+  return false;
+}
+
+void QuicSentPacketManager::RetransmitAllPackets() {
+  DVLOG(1) << "RetransmitAllPackets() called with "
+           << unacked_packets_.GetNumUnackedPacketsDebugOnly()
+           << " unacked packets.";
+  // Request retransmission of all retransmittable packets when the RTO
+  // fires, and let the congestion manager decide how many to send
+  // immediately and the remaining packets will be queued.
+  // Abandon any non-retransmittable packets that are sufficiently old.
+  bool packets_retransmitted = false;
+  QuicPacketSequenceNumber sequence_number = unacked_packets_.GetLeastUnacked();
+  for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    if (it->retransmittable_frames != nullptr) {
+      packets_retransmitted = true;
+      MarkForRetransmission(sequence_number, RTO_RETRANSMISSION);
+    } else {
+      unacked_packets_.RemoveFromInFlight(sequence_number);
+    }
+  }
+
+  send_algorithm_->OnRetransmissionTimeout(packets_retransmitted);
+  if (packets_retransmitted) {
+    if (consecutive_rto_count_ == 0) {
+      first_rto_transmission_ = unacked_packets_.largest_sent_packet() + 1;
+    }
+    ++consecutive_rto_count_;
+  }
+
+  if (network_change_visitor_ != nullptr) {
+    network_change_visitor_->OnCongestionWindowChange(GetCongestionWindow());
+  }
+}
+
+QuicSentPacketManager::RetransmissionTimeoutMode
+    QuicSentPacketManager::GetRetransmissionMode() const {
+  DCHECK(unacked_packets_.HasInFlightPackets());
+  if (!handshake_confirmed_ && unacked_packets_.HasPendingCryptoPackets()) {
+    return HANDSHAKE_MODE;
+  }
+  if (loss_algorithm_->GetLossTimeout() != QuicTime::Zero()) {
+    return LOSS_MODE;
+  }
+  if (consecutive_tlp_count_ < max_tail_loss_probes_) {
+    if (unacked_packets_.HasUnackedRetransmittableFrames()) {
+      return TLP_MODE;
+    }
+  }
+  return RTO_MODE;
+}
+
+void QuicSentPacketManager::OnIncomingQuicCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& frame,
+    const QuicTime& feedback_receive_time) {
+  send_algorithm_->OnIncomingQuicCongestionFeedbackFrame(
+      frame, feedback_receive_time);
+}
+
+void QuicSentPacketManager::InvokeLossDetection(QuicTime time) {
+  SequenceNumberSet lost_packets =
+      loss_algorithm_->DetectLostPackets(unacked_packets_,
+                                         time,
+                                         unacked_packets_.largest_observed(),
+                                         rtt_stats_);
+  for (SequenceNumberSet::const_iterator it = lost_packets.begin();
+       it != lost_packets.end(); ++it) {
+    QuicPacketSequenceNumber sequence_number = *it;
+    const TransmissionInfo& transmission_info =
+        unacked_packets_.GetTransmissionInfo(sequence_number);
+    // TODO(ianswett): If it's expected the FEC packet may repair the loss, it
+    // should be recorded as a loss to the send algorithm, but not retransmitted
+    // until it's known whether the FEC packet arrived.
+    ++stats_->packets_lost;
+    packets_lost_.push_back(make_pair(sequence_number, transmission_info));
+    DVLOG(1) << ENDPOINT << "Lost packet " << sequence_number;
+
+    if (transmission_info.retransmittable_frames != nullptr) {
+      MarkForRetransmission(sequence_number, LOSS_RETRANSMISSION);
+    } else {
+      // Since we will not retransmit this, we need to remove it from
+      // unacked_packets_.   This is either the current transmission of
+      // a packet whose previous transmission has been acked, a packet that has
+      // been TLP retransmitted, or an FEC packet.
+      unacked_packets_.RemoveFromInFlight(sequence_number);
+    }
+  }
+}
+
+bool QuicSentPacketManager::MaybeUpdateRTT(
+    const QuicAckFrame& ack_frame,
+    const QuicTime& ack_receive_time) {
+  if (!unacked_packets_.IsUnacked(ack_frame.largest_observed)) {
+    return false;
+  }
+  // We calculate the RTT based on the highest ACKed sequence number, the lower
+  // sequence numbers will include the ACK aggregation delay.
+  const TransmissionInfo& transmission_info =
+      unacked_packets_.GetTransmissionInfo(ack_frame.largest_observed);
+  // Ensure the packet has a valid sent time.
+  if (transmission_info.sent_time == QuicTime::Zero()) {
+    LOG(DFATAL) << "Acked packet has zero sent time, largest_observed:"
+                << ack_frame.largest_observed;
+    return false;
+  }
+
+  QuicTime::Delta send_delta =
+      ack_receive_time.Subtract(transmission_info.sent_time);
+  rtt_stats_.UpdateRtt(
+      send_delta, ack_frame.delta_time_largest_observed, ack_receive_time);
+  return true;
+}
+
+QuicTime::Delta QuicSentPacketManager::TimeUntilSend(
+    QuicTime now,
+    HasRetransmittableData retransmittable) {
+  // The TLP logic is entirely contained within QuicSentPacketManager, so the
+  // send algorithm does not need to be consulted.
+  if (pending_timer_transmission_count_ > 0) {
+    return QuicTime::Delta::Zero();
+  }
+  return send_algorithm_->TimeUntilSend(
+      now, unacked_packets_.bytes_in_flight(), retransmittable);
+}
+
+// Uses a 25ms delayed ack timer. Also helps with better signaling
+// in low-bandwidth (< ~384 kbps), where an ack is sent per packet.
+// Ensures that the Delayed Ack timer is always set to a value lesser
+// than the retransmission timer's minimum value (MinRTO). We want the
+// delayed ack to get back to the QUIC peer before the sender's
+// retransmission timer triggers.  Since we do not know the
+// reverse-path one-way delay, we assume equal delays for forward and
+// reverse paths, and ensure that the timer is set to less than half
+// of the MinRTO.
+// There may be a value in making this delay adaptive with the help of
+// the sender and a signaling mechanism -- if the sender uses a
+// different MinRTO, we may get spurious retransmissions. May not have
+// any benefits, but if the delayed ack becomes a significant source
+// of (likely, tail) latency, then consider such a mechanism.
+const QuicTime::Delta QuicSentPacketManager::DelayedAckTime() const {
+  return QuicTime::Delta::FromMilliseconds(min(kMaxDelayedAckTimeMs,
+                                               kMinRetransmissionTimeMs/2));
+}
+
+const QuicTime QuicSentPacketManager::GetRetransmissionTime() const {
+  // Don't set the timer if there are no packets in flight or we've already
+  // queued a tlp transmission and it hasn't been sent yet.
+  if (!unacked_packets_.HasInFlightPackets() ||
+      pending_timer_transmission_count_ > 0) {
+    return QuicTime::Zero();
+  }
+  switch (GetRetransmissionMode()) {
+    case HANDSHAKE_MODE:
+      return clock_->ApproximateNow().Add(GetCryptoRetransmissionDelay());
+    case LOSS_MODE:
+      return loss_algorithm_->GetLossTimeout();
+    case TLP_MODE: {
+      // TODO(ianswett): When CWND is available, it would be preferable to
+      // set the timer based on the earliest retransmittable packet.
+      // Base the updated timer on the send time of the last packet.
+      const QuicTime sent_time = unacked_packets_.GetLastPacketSentTime();
+      const QuicTime tlp_time = sent_time.Add(GetTailLossProbeDelay());
+      // Ensure the TLP timer never gets set to a time in the past.
+      return QuicTime::Max(clock_->ApproximateNow(), tlp_time);
+    }
+    case RTO_MODE: {
+      // The RTO is based on the first outstanding packet.
+      const QuicTime sent_time =
+          unacked_packets_.GetFirstInFlightPacketSentTime();
+      QuicTime rto_time = sent_time.Add(GetRetransmissionDelay());
+      // Wait for TLP packets to be acked before an RTO fires.
+      QuicTime tlp_time =
+          unacked_packets_.GetLastPacketSentTime().Add(GetTailLossProbeDelay());
+      return QuicTime::Max(tlp_time, rto_time);
+    }
+  }
+  DCHECK(false);
+  return QuicTime::Zero();
+}
+
+const QuicTime::Delta QuicSentPacketManager::GetCryptoRetransmissionDelay()
+    const {
+  // This is equivalent to the TailLossProbeDelay, but slightly more aggressive
+  // because crypto handshake messages don't incur a delayed ack time.
+  int64 delay_ms = max<int64>(kMinHandshakeTimeoutMs,
+                              1.5 * rtt_stats_.SmoothedRtt().ToMilliseconds());
+  return QuicTime::Delta::FromMilliseconds(
+      delay_ms << consecutive_crypto_retransmission_count_);
+}
+
+const QuicTime::Delta QuicSentPacketManager::GetTailLossProbeDelay() const {
+  QuicTime::Delta srtt = rtt_stats_.SmoothedRtt();
+  if (!unacked_packets_.HasMultipleInFlightPackets()) {
+    return QuicTime::Delta::Max(
+        srtt.Multiply(2), srtt.Multiply(1.5)
+          .Add(QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs/2)));
+  }
+  return QuicTime::Delta::FromMilliseconds(
+      max(kMinTailLossProbeTimeoutMs,
+          static_cast<int64>(2 * srtt.ToMilliseconds())));
+}
+
+const QuicTime::Delta QuicSentPacketManager::GetRetransmissionDelay() const {
+  QuicTime::Delta retransmission_delay = send_algorithm_->RetransmissionDelay();
+  // TODO(rch): This code should move to |send_algorithm_|.
+  if (retransmission_delay.IsZero()) {
+    // We are in the initial state, use default timeout values.
+    retransmission_delay =
+        QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+  } else if (retransmission_delay.ToMilliseconds() < kMinRetransmissionTimeMs) {
+    retransmission_delay =
+        QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs);
+  }
+
+  // Calculate exponential back off.
+  retransmission_delay = retransmission_delay.Multiply(
+      1 << min<size_t>(consecutive_rto_count_, kMaxRetransmissions));
+
+  if (retransmission_delay.ToMilliseconds() > kMaxRetransmissionTimeMs) {
+    return QuicTime::Delta::FromMilliseconds(kMaxRetransmissionTimeMs);
+  }
+  return retransmission_delay;
+}
+
+const RttStats* QuicSentPacketManager::GetRttStats() const {
+  return &rtt_stats_;
+}
+
+QuicBandwidth QuicSentPacketManager::BandwidthEstimate() const {
+  return send_algorithm_->BandwidthEstimate();
+}
+
+bool QuicSentPacketManager::HasReliableBandwidthEstimate() const {
+  return send_algorithm_->HasReliableBandwidthEstimate();
+}
+
+const QuicSustainedBandwidthRecorder&
+QuicSentPacketManager::SustainedBandwidthRecorder() const {
+  return sustained_bandwidth_recorder_;
+}
+
+QuicByteCount QuicSentPacketManager::GetCongestionWindow() const {
+  return send_algorithm_->GetCongestionWindow();
+}
+
+QuicByteCount QuicSentPacketManager::GetSlowStartThreshold() const {
+  return send_algorithm_->GetSlowStartThreshold();
+}
+
+void QuicSentPacketManager::EnablePacing() {
+  if (using_pacing_) {
+    return;
+  }
+
+  // Set up a pacing sender with a 5 millisecond alarm granularity.
+  using_pacing_ = true;
+  send_algorithm_.reset(
+      new PacingSender(send_algorithm_.release(),
+                       QuicTime::Delta::FromMilliseconds(5),
+                       kInitialUnpacedBurst));
+}
+
+}  // namespace net
diff --git a/net/quic/quic_sent_packet_manager.h b/net/quic/quic_sent_packet_manager.h
new file mode 100644
index 0000000..2b5f738
--- /dev/null
+++ b/net/quic/quic_sent_packet_manager.h
@@ -0,0 +1,393 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_
+#define NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/congestion_control/rtt_stats.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_ack_notifier_manager.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_sustained_bandwidth_recorder.h"
+#include "net/quic/quic_unacked_packet_map.h"
+
+namespace net {
+
+namespace test {
+class QuicConnectionPeer;
+class QuicSentPacketManagerPeer;
+}  // namespace test
+
+class QuicClock;
+class QuicConfig;
+struct QuicConnectionStats;
+
+// Class which tracks the set of packets sent on a QUIC connection and contains
+// a send algorithm to decide when to send new packets.  It keeps track of any
+// retransmittable data associated with each packet. If a packet is
+// retransmitted, it will keep track of each version of a packet so that if a
+// previous transmission is acked, the data will not be retransmitted.
+class NET_EXPORT_PRIVATE QuicSentPacketManager {
+ public:
+  // Interface which gets callbacks from the QuicSentPacketManager at
+  // interesting points.  Implementations must not mutate the state of
+  // the packet manager or connection as a result of these callbacks.
+  class NET_EXPORT_PRIVATE DebugDelegate {
+   public:
+    virtual ~DebugDelegate() {}
+
+    // Called when a spurious retransmission is detected.
+    virtual void OnSpuriousPacketRetransmition(
+        TransmissionType transmission_type,
+        QuicByteCount byte_size) {}
+
+    virtual void OnSentPacket(
+        const SerializedPacket& packet,
+        QuicPacketSequenceNumber original_sequence_number,
+        QuicTime sent_time,
+        QuicByteCount bytes,
+        TransmissionType transmission_type) {}
+
+    virtual void OnIncomingAck(
+        const QuicAckFrame& ack_frame,
+        QuicTime ack_receive_time,
+        QuicPacketSequenceNumber largest_observed,
+        bool largest_observed_acked,
+        QuicPacketSequenceNumber least_unacked_sent_packet) {}
+  };
+
+  // Interface which gets callbacks from the QuicSentPacketManager when
+  // network-related state changes. Implementations must not mutate the
+  // state of the packet manager as a result of these callbacks.
+  class NET_EXPORT_PRIVATE NetworkChangeVisitor {
+   public:
+    virtual ~NetworkChangeVisitor() {}
+
+    // Called when congestion window may have changed.
+    virtual void OnCongestionWindowChange(QuicByteCount congestion_window) = 0;
+    // TODO(jri): Add OnRttStatsChange() to this class as well.
+  };
+
+  // Struct to store the pending retransmission information.
+  struct PendingRetransmission {
+    PendingRetransmission(QuicPacketSequenceNumber sequence_number,
+                          TransmissionType transmission_type,
+                          const RetransmittableFrames& retransmittable_frames,
+                          QuicSequenceNumberLength sequence_number_length)
+            : sequence_number(sequence_number),
+              transmission_type(transmission_type),
+              retransmittable_frames(retransmittable_frames),
+              sequence_number_length(sequence_number_length) {
+        }
+
+        QuicPacketSequenceNumber sequence_number;
+        TransmissionType transmission_type;
+        const RetransmittableFrames& retransmittable_frames;
+        QuicSequenceNumberLength sequence_number_length;
+  };
+
+  QuicSentPacketManager(bool is_server,
+                        const QuicClock* clock,
+                        QuicConnectionStats* stats,
+                        CongestionControlType congestion_control_type,
+                        LossDetectionType loss_type);
+  virtual ~QuicSentPacketManager();
+
+  virtual void SetFromConfig(const QuicConfig& config);
+
+  void SetHandshakeConfirmed() { handshake_confirmed_ = true; }
+
+  // Processes the incoming ack.
+  void OnIncomingAck(const QuicAckFrame& ack_frame,
+                     QuicTime ack_receive_time);
+
+  // Returns true if the non-FEC packet |sequence_number| is unacked.
+  bool IsUnacked(QuicPacketSequenceNumber sequence_number) const;
+
+  // Requests retransmission of all unacked packets of |retransmission_type|.
+  void RetransmitUnackedPackets(TransmissionType retransmission_type);
+
+  // Retransmits the oldest pending packet there is still a tail loss probe
+  // pending.  Invoked after OnRetransmissionTimeout.
+  bool MaybeRetransmitTailLossProbe();
+
+  // Removes the retransmittable frames from all unencrypted packets to ensure
+  // they don't get retransmitted.
+  void NeuterUnencryptedPackets();
+
+  // Returns true if the unacked packet |sequence_number| has retransmittable
+  // frames.  This will only return false if the packet has been acked, if a
+  // previous transmission of this packet was ACK'd, or if this packet has been
+  // retransmitted as with different sequence number.
+  bool HasRetransmittableFrames(QuicPacketSequenceNumber sequence_number) const;
+
+  // Returns true if there are pending retransmissions.
+  bool HasPendingRetransmissions() const;
+
+  // Retrieves the next pending retransmission.
+  PendingRetransmission NextPendingRetransmission();
+
+  bool HasUnackedPackets() const;
+
+  // Returns the smallest sequence number of a serialized packet which has not
+  // been acked by the peer.
+  QuicPacketSequenceNumber GetLeastUnacked() const;
+
+  // Called when a congestion feedback frame is received from peer.
+  virtual void OnIncomingQuicCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame,
+      const QuicTime& feedback_receive_time);
+
+  // Called when we have sent bytes to the peer.  This informs the manager both
+  // the number of bytes sent and if they were retransmitted.  Returns true if
+  // the sender should reset the retransmission timer.
+  virtual bool OnPacketSent(SerializedPacket* serialized_packet,
+                            QuicPacketSequenceNumber original_sequence_number,
+                            QuicTime sent_time,
+                            QuicByteCount bytes,
+                            TransmissionType transmission_type,
+                            HasRetransmittableData has_retransmittable_data);
+
+  // Called when the retransmission timer expires.
+  virtual void OnRetransmissionTimeout();
+
+  // Calculate the time until we can send the next packet to the wire.
+  // Note 1: When kUnknownWaitTime is returned, there is no need to poll
+  // TimeUntilSend again until we receive an OnIncomingAckFrame event.
+  // Note 2: Send algorithms may or may not use |retransmit| in their
+  // calculations.
+  virtual QuicTime::Delta TimeUntilSend(QuicTime now,
+                                        HasRetransmittableData retransmittable);
+
+  // Returns amount of time for delayed ack timer.
+  const QuicTime::Delta DelayedAckTime() const;
+
+  // Returns the current delay for the retransmission timer, which may send
+  // either a tail loss probe or do a full RTO.  Returns QuicTime::Zero() if
+  // there are no retransmittable packets.
+  const QuicTime GetRetransmissionTime() const;
+
+  const RttStats* GetRttStats() const;
+
+  // Returns the estimated bandwidth calculated by the congestion algorithm.
+  QuicBandwidth BandwidthEstimate() const;
+
+  // Returns true if the current instantaneous bandwidth estimate is reliable.
+  bool HasReliableBandwidthEstimate() const;
+
+  const QuicSustainedBandwidthRecorder& SustainedBandwidthRecorder() const;
+
+  // Returns the size of the current congestion window in bytes.  Note, this is
+  // not the *available* window.  Some send algorithms may not use a congestion
+  // window and will return 0.
+  QuicByteCount GetCongestionWindow() const;
+
+  // Returns the size of the slow start congestion window in bytes,
+  // aka ssthresh.  Some send algorithms do not define a slow start
+  // threshold and will return 0.
+  QuicByteCount GetSlowStartThreshold() const;
+
+  // Enables pacing if it has not already been enabled.
+  void EnablePacing();
+
+  bool using_pacing() const { return using_pacing_; }
+
+  void set_debug_delegate(DebugDelegate* debug_delegate) {
+    debug_delegate_ = debug_delegate;
+  }
+
+  QuicPacketSequenceNumber largest_observed() const {
+    return unacked_packets_.largest_observed();
+  }
+
+  QuicPacketSequenceNumber least_packet_awaited_by_peer() {
+    return least_packet_awaited_by_peer_;
+  }
+
+  void set_network_change_visitor(NetworkChangeVisitor* visitor) {
+    DCHECK(!network_change_visitor_);
+    DCHECK(visitor);
+    network_change_visitor_ = visitor;
+  }
+
+  size_t consecutive_rto_count() const {
+    return consecutive_rto_count_;
+  }
+
+  size_t consecutive_tlp_count() const {
+    return consecutive_tlp_count_;
+  }
+
+ private:
+  friend class test::QuicConnectionPeer;
+  friend class test::QuicSentPacketManagerPeer;
+
+  // The retransmission timer is a single timer which switches modes depending
+  // upon connection state.
+  enum RetransmissionTimeoutMode {
+    // A conventional TCP style RTO.
+    RTO_MODE,
+    // A tail loss probe.  By default, QUIC sends up to two before RTOing.
+    TLP_MODE,
+    // Retransmission of handshake packets prior to handshake completion.
+    HANDSHAKE_MODE,
+    // Re-invoke the loss detection when a packet is not acked before the
+    // loss detection algorithm expects.
+    LOSS_MODE,
+  };
+
+  typedef linked_hash_map<QuicPacketSequenceNumber,
+                          TransmissionType> PendingRetransmissionMap;
+
+  // Called when a packet is retransmitted with a new sequence number.
+  // Replaces the old entry in the unacked packet map with the new
+  // sequence number.
+  void OnRetransmittedPacket(QuicPacketSequenceNumber old_sequence_number,
+                             QuicPacketSequenceNumber new_sequence_number);
+
+  // Updates the least_packet_awaited_by_peer.
+  void UpdatePacketInformationReceivedByPeer(const QuicAckFrame& ack_frame);
+
+  // Process the incoming ack looking for newly ack'd data packets.
+  void HandleAckForSentPackets(const QuicAckFrame& ack_frame);
+
+  // Returns the current retransmission mode.
+  RetransmissionTimeoutMode GetRetransmissionMode() const;
+
+  // Retransmits all crypto stream packets.
+  void RetransmitCryptoPackets();
+
+  // Retransmits all the packets and abandons by invoking a full RTO.
+  void RetransmitAllPackets();
+
+  // Returns the timer for retransmitting crypto handshake packets.
+  const QuicTime::Delta GetCryptoRetransmissionDelay() const;
+
+  // Returns the timer for a new tail loss probe.
+  const QuicTime::Delta GetTailLossProbeDelay() const;
+
+  // Returns the retransmission timeout, after which a full RTO occurs.
+  const QuicTime::Delta GetRetransmissionDelay() const;
+
+  // Update the RTT if the ack is for the largest acked sequence number.
+  // Returns true if the rtt was updated.
+  bool MaybeUpdateRTT(const QuicAckFrame& ack_frame,
+                      const QuicTime& ack_receive_time);
+
+  // Invokes the loss detection algorithm and loses and retransmits packets if
+  // necessary.
+  void InvokeLossDetection(QuicTime time);
+
+  // Invokes OnCongestionEvent if |rtt_updated| is true, there are pending acks,
+  // or pending losses.  Clears pending acks and pending losses afterwards.
+  // |bytes_in_flight| is the number of bytes in flight before the losses or
+  // acks.
+  void MaybeInvokeCongestionEvent(bool rtt_updated,
+                                  QuicByteCount bytes_in_flight);
+
+  // Marks |sequence_number| as having been revived by the peer, but not
+  // received, so the packet remains pending if it is and the congestion control
+  // does not consider the packet acked.
+  void MarkPacketRevived(QuicPacketSequenceNumber sequence_number,
+                         QuicTime::Delta delta_largest_observed);
+
+  // Removes the retransmittability and pending properties from the packet at
+  // |it| due to receipt by the peer.  Returns an iterator to the next remaining
+  // unacked packet.
+  void MarkPacketHandled(QuicPacketSequenceNumber sequence_number,
+                         const TransmissionInfo& info,
+                         QuicTime::Delta delta_largest_observed);
+
+  // Request that |sequence_number| be retransmitted after the other pending
+  // retransmissions.  Does not add it to the retransmissions if it's already
+  // a pending retransmission.
+  void MarkForRetransmission(QuicPacketSequenceNumber sequence_number,
+                             TransmissionType transmission_type);
+
+  // Notify observers about spurious retransmits.
+  void RecordSpuriousRetransmissions(
+      const SequenceNumberList& all_transmissions,
+      QuicPacketSequenceNumber acked_sequence_number);
+
+  // Returns true if the client is sending or the server has received a
+  // connection option.
+  bool HasClientSentConnectionOption(const QuicConfig& config,
+                                     QuicTag tag) const;
+
+  // Newly serialized retransmittable and fec packets are added to this map,
+  // which contains owning pointers to any contained frames.  If a packet is
+  // retransmitted, this map will contain entries for both the old and the new
+  // packet. The old packet's retransmittable frames entry will be nullptr,
+  // while the new packet's entry will contain the frames to retransmit.
+  // If the old packet is acked before the new packet, then the old entry will
+  // be removed from the map and the new entry's retransmittable frames will be
+  // set to nullptr.
+  QuicUnackedPacketMap unacked_packets_;
+
+  // Pending retransmissions which have not been packetized and sent yet.
+  PendingRetransmissionMap pending_retransmissions_;
+
+  // Tracks if the connection was created by the server.
+  bool is_server_;
+
+  // An AckNotifier can register to be informed when ACKs have been received for
+  // all packets that a given block of data was sent in. The AckNotifierManager
+  // maintains the currently active notifiers.
+  AckNotifierManager ack_notifier_manager_;
+
+  const QuicClock* clock_;
+  QuicConnectionStats* stats_;
+  DebugDelegate* debug_delegate_;
+  NetworkChangeVisitor* network_change_visitor_;
+  RttStats rtt_stats_;
+  scoped_ptr<SendAlgorithmInterface> send_algorithm_;
+  scoped_ptr<LossDetectionInterface> loss_algorithm_;
+
+  // Least sequence number which the peer is still waiting for.
+  QuicPacketSequenceNumber least_packet_awaited_by_peer_;
+
+  // Tracks the first RTO packet.  If any packet before that packet gets acked,
+  // it indicates the RTO was spurious and should be reversed(F-RTO).
+  QuicPacketSequenceNumber first_rto_transmission_;
+  // Number of times the RTO timer has fired in a row without receiving an ack.
+  size_t consecutive_rto_count_;
+  // Number of times the tail loss probe has been sent.
+  size_t consecutive_tlp_count_;
+  // Number of times the crypto handshake has been retransmitted.
+  size_t consecutive_crypto_retransmission_count_;
+  // Number of pending transmissions of TLP or crypto packets.
+  size_t pending_timer_transmission_count_;
+  // Maximum number of tail loss probes to send before firing an RTO.
+  size_t max_tail_loss_probes_;
+  bool using_pacing_;
+
+  // Vectors packets acked and lost as a result of the last congestion event.
+  SendAlgorithmInterface::CongestionVector packets_acked_;
+  SendAlgorithmInterface::CongestionVector packets_lost_;
+
+  // Set to true after the crypto handshake has successfully completed. After
+  // this is true we no longer use HANDSHAKE_MODE, and further frames sent on
+  // the crypto stream (i.e. SCUP messages) are treated like normal
+  // retransmittable frames.
+  bool handshake_confirmed_;
+
+  // Records bandwidth from server to client in normal operation, over periods
+  // of time with no loss events.
+  QuicSustainedBandwidthRecorder sustained_bandwidth_recorder_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSentPacketManager);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SENT_PACKET_MANAGER_H_
diff --git a/net/quic/quic_sent_packet_manager_test.cc b/net/quic/quic_sent_packet_manager_test.cc
new file mode 100644
index 0000000..42c9a6f
--- /dev/null
+++ b/net/quic/quic_sent_packet_manager_test.cc
@@ -0,0 +1,1390 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sent_packet_manager.h"
+
+#include "base/stl_util.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/test_tools/quic_config_peer.h"
+#include "net/quic/test_tools/quic_sent_packet_manager_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::vector;
+using testing::AnyNumber;
+using testing::ElementsAre;
+using testing::Pair;
+using testing::Pointwise;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+// Default packet length.
+const uint32 kDefaultLength = 1000;
+
+// Matcher to check the key of the key-value pair it receives as first argument
+// equals its second argument.
+MATCHER(KeyEq, "") {
+  return std::tr1::get<0>(arg).first == std::tr1::get<1>(arg);
+}
+
+class MockDebugDelegate : public QuicSentPacketManager::DebugDelegate {
+ public:
+  MOCK_METHOD2(OnSpuriousPacketRetransmition,
+               void(TransmissionType transmission_type,
+                    QuicByteCount byte_size));
+};
+
+class QuicSentPacketManagerTest : public ::testing::TestWithParam<bool> {
+ protected:
+  QuicSentPacketManagerTest()
+      : manager_(true, &clock_, &stats_, kCubic, kNack),
+        send_algorithm_(new StrictMock<MockSendAlgorithm>),
+        network_change_visitor_(new StrictMock<MockNetworkChangeVisitor>) {
+    QuicSentPacketManagerPeer::SetSendAlgorithm(&manager_, send_algorithm_);
+    // Disable tail loss probes for most tests.
+    QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 0);
+    // Advance the time 1s so the send times are never QuicTime::Zero.
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000));
+    manager_.set_network_change_visitor(network_change_visitor_.get());
+
+    EXPECT_CALL(*send_algorithm_, HasReliableBandwidthEstimate())
+        .Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, BandwidthEstimate())
+        .Times(AnyNumber())
+        .WillRepeatedly(Return(QuicBandwidth::Zero()));
+    EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber());
+    EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber());
+  }
+
+  virtual ~QuicSentPacketManagerTest() OVERRIDE {
+    STLDeleteElements(&packets_);
+  }
+
+  QuicByteCount BytesInFlight() {
+    return QuicSentPacketManagerPeer::GetBytesInFlight(&manager_);
+  }
+  void VerifyUnackedPackets(QuicPacketSequenceNumber* packets,
+                            size_t num_packets) {
+    if (num_packets == 0) {
+      EXPECT_FALSE(manager_.HasUnackedPackets());
+      EXPECT_EQ(0u, QuicSentPacketManagerPeer::GetNumRetransmittablePackets(
+          &manager_));
+      return;
+    }
+
+    EXPECT_TRUE(manager_.HasUnackedPackets());
+    EXPECT_EQ(packets[0], manager_.GetLeastUnacked());
+    for (size_t i = 0; i < num_packets; ++i) {
+      EXPECT_TRUE(manager_.IsUnacked(packets[i])) << packets[i];
+    }
+  }
+
+  void VerifyRetransmittablePackets(QuicPacketSequenceNumber* packets,
+                                    size_t num_packets) {
+    EXPECT_EQ(num_packets,
+              QuicSentPacketManagerPeer::GetNumRetransmittablePackets(
+                  &manager_));
+    for (size_t i = 0; i < num_packets; ++i) {
+      EXPECT_TRUE(manager_.HasRetransmittableFrames(packets[i]))
+          << " packets[" << i << "]:" << packets[i];
+    }
+  }
+
+  void ExpectAck(QuicPacketSequenceNumber largest_observed) {
+    EXPECT_CALL(*send_algorithm_, OnCongestionEvent(
+        true, _, ElementsAre(Pair(largest_observed, _)), _));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillOnce(Return(100 * kDefaultTCPMSS));
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  }
+
+  void ExpectUpdatedRtt(QuicPacketSequenceNumber largest_observed) {
+    EXPECT_CALL(*send_algorithm_,
+                OnCongestionEvent(true, _, _, _));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillOnce(Return(100 * kDefaultTCPMSS));
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  }
+
+  void ExpectAckAndLoss(bool rtt_updated,
+                        QuicPacketSequenceNumber largest_observed,
+                        QuicPacketSequenceNumber lost_packet) {
+    EXPECT_CALL(*send_algorithm_, OnCongestionEvent(
+        rtt_updated, _, ElementsAre(Pair(largest_observed, _)),
+        ElementsAre(Pair(lost_packet, _))));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillOnce(Return(100 * kDefaultTCPMSS));
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  }
+
+  // |packets_acked| and |packets_lost| should be in sequence number order.
+  void ExpectAcksAndLosses(bool rtt_updated,
+                           QuicPacketSequenceNumber* packets_acked,
+                           size_t num_packets_acked,
+                           QuicPacketSequenceNumber* packets_lost,
+                           size_t num_packets_lost) {
+    vector<QuicPacketSequenceNumber> ack_vector;
+    for (size_t i = 0; i < num_packets_acked; ++i) {
+      ack_vector.push_back(packets_acked[i]);
+    }
+    vector<QuicPacketSequenceNumber> lost_vector;
+    for (size_t i = 0; i < num_packets_lost; ++i) {
+      lost_vector.push_back(packets_lost[i]);
+    }
+    EXPECT_CALL(*send_algorithm_,
+                OnCongestionEvent(rtt_updated, _,
+                                  Pointwise(KeyEq(), ack_vector),
+                                  Pointwise(KeyEq(), lost_vector)));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillRepeatedly(Return(100 * kDefaultTCPMSS));
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_)).
+        Times(AnyNumber());
+  }
+
+  void RetransmitAndSendPacket(QuicPacketSequenceNumber old_sequence_number,
+                               QuicPacketSequenceNumber new_sequence_number) {
+    QuicSentPacketManagerPeer::MarkForRetransmission(
+        &manager_, old_sequence_number, TLP_RETRANSMISSION);
+    EXPECT_TRUE(manager_.HasPendingRetransmissions());
+    QuicSentPacketManager::PendingRetransmission next_retransmission =
+        manager_.NextPendingRetransmission();
+    EXPECT_EQ(old_sequence_number, next_retransmission.sequence_number);
+    EXPECT_EQ(TLP_RETRANSMISSION,
+              next_retransmission.transmission_type);
+
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, BytesInFlight(), new_sequence_number,
+                             kDefaultLength, HAS_RETRANSMITTABLE_DATA))
+        .WillOnce(Return(true));
+    SerializedPacket packet(CreatePacket(new_sequence_number, false));
+    manager_.OnPacketSent(&packet,
+                          old_sequence_number,
+                          clock_.Now(),
+                          kDefaultLength,
+                          LOSS_RETRANSMISSION,
+                          HAS_RETRANSMITTABLE_DATA);
+    EXPECT_TRUE(QuicSentPacketManagerPeer::IsRetransmission(
+        &manager_, new_sequence_number));
+  }
+
+  SerializedPacket CreateDataPacket(QuicPacketSequenceNumber sequence_number) {
+    return CreatePacket(sequence_number, true);
+  }
+
+  SerializedPacket CreatePacket(QuicPacketSequenceNumber sequence_number,
+                                bool retransmittable) {
+    packets_.push_back(QuicPacket::NewDataPacket(
+        nullptr, kDefaultLength, false, PACKET_8BYTE_CONNECTION_ID, false,
+        PACKET_6BYTE_SEQUENCE_NUMBER));
+    return SerializedPacket(
+        sequence_number, PACKET_6BYTE_SEQUENCE_NUMBER, packets_.back(), 0u,
+        retransmittable ? new RetransmittableFrames() : nullptr);
+  }
+
+  SerializedPacket CreateFecPacket(QuicPacketSequenceNumber sequence_number) {
+    packets_.push_back(QuicPacket::NewFecPacket(
+        nullptr, kDefaultLength, false, PACKET_8BYTE_CONNECTION_ID, false,
+        PACKET_6BYTE_SEQUENCE_NUMBER));
+    return SerializedPacket(sequence_number, PACKET_6BYTE_SEQUENCE_NUMBER,
+                            packets_.back(), 0u, nullptr);
+  }
+
+  void SendDataPacket(QuicPacketSequenceNumber sequence_number) {
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, BytesInFlight(), sequence_number, _, _))
+                    .Times(1).WillOnce(Return(true));
+    SerializedPacket packet(CreateDataPacket(sequence_number));
+    manager_.OnPacketSent(&packet, 0, clock_.Now(),
+                          packet.packet->length(), NOT_RETRANSMISSION,
+                          HAS_RETRANSMITTABLE_DATA);
+  }
+
+  void SendCryptoPacket(QuicPacketSequenceNumber sequence_number) {
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, BytesInFlight(), sequence_number,
+                             kDefaultLength, HAS_RETRANSMITTABLE_DATA))
+                    .Times(1).WillOnce(Return(true));
+    SerializedPacket packet(CreateDataPacket(sequence_number));
+    packet.retransmittable_frames->AddStreamFrame(
+        new QuicStreamFrame(1, false, 0, IOVector()));
+    packet.retransmittable_frames->set_encryption_level(ENCRYPTION_NONE);
+    manager_.OnPacketSent(&packet, 0, clock_.Now(),
+                          packet.packet->length(), NOT_RETRANSMISSION,
+                          HAS_RETRANSMITTABLE_DATA);
+  }
+
+  void SendFecPacket(QuicPacketSequenceNumber sequence_number) {
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, BytesInFlight(), sequence_number,
+                             kDefaultLength, NO_RETRANSMITTABLE_DATA))
+                    .Times(1).WillOnce(Return(true));
+    SerializedPacket packet(CreateFecPacket(sequence_number));
+    manager_.OnPacketSent(&packet, 0, clock_.Now(),
+                          packet.packet->length(), NOT_RETRANSMISSION,
+                          NO_RETRANSMITTABLE_DATA);
+  }
+
+  void SendAckPacket(QuicPacketSequenceNumber sequence_number) {
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, BytesInFlight(), sequence_number,
+                             kDefaultLength, NO_RETRANSMITTABLE_DATA))
+                    .Times(1).WillOnce(Return(false));
+    SerializedPacket packet(CreatePacket(sequence_number, false));
+    manager_.OnPacketSent(&packet, 0, clock_.Now(),
+                          packet.packet->length(), NOT_RETRANSMISSION,
+                          NO_RETRANSMITTABLE_DATA);
+  }
+
+  // Based on QuicConnection's WritePendingRetransmissions.
+  void RetransmitNextPacket(
+      QuicPacketSequenceNumber retransmission_sequence_number) {
+    EXPECT_TRUE(manager_.HasPendingRetransmissions());
+    EXPECT_CALL(*send_algorithm_,
+                OnPacketSent(_, _, retransmission_sequence_number,
+                             kDefaultLength, HAS_RETRANSMITTABLE_DATA))
+                    .Times(1).WillOnce(Return(true));
+    const QuicSentPacketManager::PendingRetransmission pending =
+        manager_.NextPendingRetransmission();
+    SerializedPacket packet(
+        CreatePacket(retransmission_sequence_number, false));
+    manager_.OnPacketSent(&packet, pending.sequence_number, clock_.Now(),
+                          kDefaultLength, pending.transmission_type,
+                          HAS_RETRANSMITTABLE_DATA);
+  }
+
+  QuicSentPacketManager manager_;
+  vector<QuicPacket*> packets_;
+  MockClock clock_;
+  QuicConnectionStats stats_;
+  MockSendAlgorithm* send_algorithm_;
+  scoped_ptr<MockNetworkChangeVisitor> network_change_visitor_;
+};
+
+TEST_F(QuicSentPacketManagerTest, IsUnacked) {
+  VerifyUnackedPackets(nullptr, 0);
+  SendDataPacket(1);
+
+  QuicPacketSequenceNumber unacked[] = { 1 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 1 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+}
+
+TEST_F(QuicSentPacketManagerTest, IsUnAckedRetransmit) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+
+  EXPECT_TRUE(QuicSentPacketManagerPeer::IsRetransmission(&manager_, 2));
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 2 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitThenAck) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+
+  // Ack 2 but not 1.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.missing_packets.insert(1);
+  ExpectAck(2);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  // Packet 1 is unacked, pending, but not retransmittable.
+  QuicPacketSequenceNumber unacked[] = { 1 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitThenAckBeforeSend) {
+  SendDataPacket(1);
+  QuicSentPacketManagerPeer::MarkForRetransmission(
+      &manager_, 1, TLP_RETRANSMISSION);
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+
+  // Ack 1.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 1;
+  ExpectAck(1);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  // There should no longer be a pending retransmission.
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  // No unacked packets remain.
+  VerifyUnackedPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+  EXPECT_EQ(0u, stats_.packets_spuriously_retransmitted);
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitThenAckPrevious) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+  QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(15);
+  clock_.AdvanceTime(rtt);
+
+  // Ack 1 but not 2.
+  ExpectAck(1);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 1;
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  // 2 remains unacked, but no packets have retransmittable data.
+  QuicPacketSequenceNumber unacked[] = { 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  EXPECT_EQ(1u, stats_.packets_spuriously_retransmitted);
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitThenAckPreviousThenNackRetransmit) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+  QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(15);
+  clock_.AdvanceTime(rtt);
+
+  // First, ACK packet 1 which makes packet 2 non-retransmittable.
+  ExpectAck(1);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 1;
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  SendDataPacket(3);
+  SendDataPacket(4);
+  SendDataPacket(5);
+  clock_.AdvanceTime(rtt);
+
+  // Next, NACK packet 2 three times.
+  ack_frame.largest_observed = 3;
+  ack_frame.missing_packets.insert(2);
+  ExpectAck(3);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  ack_frame.largest_observed = 4;
+  ExpectAck(4);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  ack_frame.largest_observed = 5;
+  ExpectAckAndLoss(true, 5, 2);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  // No packets remain unacked.
+  VerifyUnackedPackets(nullptr, 0);
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  // Verify that the retransmission alarm would not fire,
+  // since there is no retransmittable data outstanding.
+  EXPECT_EQ(QuicTime::Zero(), manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitTwiceThenAckPreviousBeforeSend) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+
+  // Fire the RTO, which will mark 2 for retransmission (but will not send it).
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(2 * kDefaultTCPMSS));
+  manager_.OnRetransmissionTimeout();
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+
+  // Ack 1 but not 2, before 2 is able to be sent.
+  // Since 1 has been retransmitted, it has already been lost, and so the
+  // send algorithm is not informed that it has been ACK'd.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 1;
+  ExpectUpdatedRtt(1);
+  EXPECT_CALL(*send_algorithm_, RevertRetransmissionTimeout());
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  // Since 2 was marked for retransmit, when 1 is acked, 2 is kept for RTT.
+  QuicPacketSequenceNumber unacked[] = { 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  // Verify that the retransmission alarm would not fire,
+  // since there is no retransmittable data outstanding.
+  EXPECT_EQ(QuicTime::Zero(), manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmitTwiceThenAckFirst) {
+  StrictMock<MockDebugDelegate> debug_delegate;
+  EXPECT_CALL(debug_delegate, OnSpuriousPacketRetransmition(
+      TLP_RETRANSMISSION, kDefaultLength)).Times(2);
+  manager_.set_debug_delegate(&debug_delegate);
+
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+  RetransmitAndSendPacket(2, 3);
+  QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(15);
+  clock_.AdvanceTime(rtt);
+
+  // Ack 1 but not 2 or 3.
+  ExpectAck(1);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 1;
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  // 2 and 3 remain unacked, but no packets have retransmittable data.
+  QuicPacketSequenceNumber unacked[] = { 2, 3 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  // Ensure packet 2 is lost when 4 is sent and 3 and 4 are acked.
+  SendDataPacket(4);
+  ack_frame.largest_observed = 4;
+  ack_frame.missing_packets.insert(2);
+  QuicPacketSequenceNumber acked[] = { 3, 4 };
+  ExpectAcksAndLosses(true, acked, arraysize(acked), nullptr, 0);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  QuicPacketSequenceNumber unacked2[] = { 2 };
+  VerifyUnackedPackets(unacked2, arraysize(unacked2));
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+
+  SendDataPacket(5);
+  ack_frame.largest_observed = 5;
+  ExpectAckAndLoss(true, 5, 2);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  VerifyUnackedPackets(nullptr, 0);
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  EXPECT_EQ(2u, stats_.packets_spuriously_retransmitted);
+}
+
+TEST_F(QuicSentPacketManagerTest, LoseButDontRetransmitRevivedPacket) {
+  SendDataPacket(1);
+  SendDataPacket(2);
+  SendFecPacket(3);
+  SendDataPacket(4);
+
+  // Ack 2 and 3, and mark 1 as revived.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 3;
+  ack_frame.missing_packets.insert(1);
+  ack_frame.revived_packets.insert(1);
+  QuicPacketSequenceNumber acked[] = { 2, 3 };
+  ExpectAcksAndLosses(true, acked, arraysize(acked), nullptr, 0);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  QuicPacketSequenceNumber unacked[] = { 1, 4 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  QuicPacketSequenceNumber retransmittable[] = { 4 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+
+  // Ack the 4th packet and expect the 1st to be considered lost.
+  ack_frame.largest_observed = 4;
+  ExpectAckAndLoss(true, 4, 1);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicSentPacketManagerTest, MarkLostThenReviveAndDontRetransmitPacket) {
+  SendDataPacket(1);
+  SendDataPacket(2);
+  SendDataPacket(3);
+  SendDataPacket(4);
+  SendFecPacket(5);
+
+  // Ack 2, 3, and 4, and expect the 1st to be considered lost.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 4;
+  ack_frame.missing_packets.insert(1);
+  QuicPacketSequenceNumber acked[] = { 2, 3, 4 };
+  QuicPacketSequenceNumber lost[] = { 1 };
+  ExpectAcksAndLosses(true, acked, arraysize(acked), lost, arraysize(lost));
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  QuicPacketSequenceNumber unacked[] = { 1, 5 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 1 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+
+  // Ack 5th packet (FEC) and revive 1st packet. 1st packet should now be
+  // removed from pending retransmissions map.
+  ack_frame.largest_observed = 5;
+  ack_frame.revived_packets.insert(1);
+  ExpectAck(5);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicSentPacketManagerTest, TruncatedAck) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+  RetransmitAndSendPacket(2, 3);
+  RetransmitAndSendPacket(3, 4);
+  RetransmitAndSendPacket(4, 5);
+
+  // Truncated ack with 4 NACKs, so the first packet is lost.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 4;
+  ack_frame.missing_packets.insert(1);
+  ack_frame.missing_packets.insert(2);
+  ack_frame.missing_packets.insert(3);
+  ack_frame.missing_packets.insert(4);
+  ack_frame.is_truncated = true;
+
+  QuicPacketSequenceNumber lost[] = { 1 };
+  ExpectAcksAndLosses(true, nullptr, 0, lost, arraysize(lost));
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  // High water mark will be raised.
+  QuicPacketSequenceNumber unacked[] = { 2, 3, 4, 5 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 5 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+}
+
+TEST_F(QuicSentPacketManagerTest, AckPreviousTransmissionThenTruncatedAck) {
+  SendDataPacket(1);
+  RetransmitAndSendPacket(1, 2);
+  RetransmitAndSendPacket(2, 3);
+  RetransmitAndSendPacket(3, 4);
+  SendDataPacket(5);
+  SendDataPacket(6);
+  SendDataPacket(7);
+  SendDataPacket(8);
+  SendDataPacket(9);
+
+  // Ack previous transmission
+  {
+    QuicAckFrame ack_frame;
+    ack_frame.largest_observed = 2;
+    ack_frame.missing_packets.insert(1);
+    ExpectAck(2);
+    manager_.OnIncomingAck(ack_frame, clock_.Now());
+    EXPECT_TRUE(manager_.IsUnacked(4));
+  }
+
+  // Truncated ack with 4 NACKs
+  {
+    QuicAckFrame ack_frame;
+    ack_frame.largest_observed = 6;
+    ack_frame.missing_packets.insert(3);
+    ack_frame.missing_packets.insert(4);
+    ack_frame.missing_packets.insert(5);
+    ack_frame.missing_packets.insert(6);
+    ack_frame.is_truncated = true;
+    ExpectAckAndLoss(true, 1, 3);
+    manager_.OnIncomingAck(ack_frame, clock_.Now());
+  }
+
+  // High water mark will be raised.
+  QuicPacketSequenceNumber unacked[] = { 4, 5, 6, 7, 8, 9 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 5, 6, 7, 8, 9 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+}
+
+TEST_F(QuicSentPacketManagerTest, GetLeastUnacked) {
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetLeastUnackedUnacked) {
+  SendDataPacket(1);
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetLeastUnackedUnackedFec) {
+  SendFecPacket(1);
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetLeastUnackedAndDiscard) {
+  VerifyUnackedPackets(nullptr, 0);
+
+  SendFecPacket(1);
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+
+  SendFecPacket(2);
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+
+  SendFecPacket(3);
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+
+  QuicPacketSequenceNumber unacked[] = { 1, 2, 3 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  // Ack 2, so there's an rtt update.
+  ExpectAck(2);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.missing_packets.insert(1);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  EXPECT_EQ(1u, manager_.GetLeastUnacked());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetSentTime) {
+  VerifyUnackedPackets(nullptr, 0);
+
+  QuicTime sent_time = clock_.Now();
+  SendFecPacket(1);
+  QuicTime sent_time2 = clock_.Now();
+  SendFecPacket(2);
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  EXPECT_TRUE(manager_.HasUnackedPackets());
+  EXPECT_EQ(sent_time, QuicSentPacketManagerPeer::GetSentTime(&manager_, 1));
+  EXPECT_EQ(sent_time2, QuicSentPacketManagerPeer::GetSentTime(&manager_, 2));
+}
+
+TEST_F(QuicSentPacketManagerTest, AckAckAndUpdateRtt) {
+  SendDataPacket(1);
+  SendAckPacket(2);
+
+  // Now ack the ack and expect an RTT update.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.delta_time_largest_observed =
+      QuicTime::Delta::FromMilliseconds(5);
+
+  ExpectAck(1);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  SendAckPacket(3);
+
+  // Now ack the ack and expect only an RTT update.
+  ack_frame.largest_observed = 3;
+  ExpectUpdatedRtt(3);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+}
+
+TEST_F(QuicSentPacketManagerTest, Rtt) {
+  QuicPacketSequenceNumber sequence_number = 1;
+  QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(15);
+  SendDataPacket(sequence_number);
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
+
+  ExpectAck(sequence_number);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = sequence_number;
+  ack_frame.delta_time_largest_observed =
+      QuicTime::Delta::FromMilliseconds(5);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+  EXPECT_EQ(expected_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->latest_rtt());
+}
+
+TEST_F(QuicSentPacketManagerTest, RttWithInvalidDelta) {
+  // Expect that the RTT is equal to the local time elapsed, since the
+  // delta_time_largest_observed is larger than the local time elapsed
+  // and is hence invalid.
+  QuicPacketSequenceNumber sequence_number = 1;
+  QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(10);
+  SendDataPacket(sequence_number);
+  clock_.AdvanceTime(expected_rtt);
+
+  ExpectAck(sequence_number);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = sequence_number;
+  ack_frame.delta_time_largest_observed =
+      QuicTime::Delta::FromMilliseconds(11);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+  EXPECT_EQ(expected_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->latest_rtt());
+}
+
+TEST_F(QuicSentPacketManagerTest, RttWithInfiniteDelta) {
+  // Expect that the RTT is equal to the local time elapsed, since the
+  // delta_time_largest_observed is infinite, and is hence invalid.
+  QuicPacketSequenceNumber sequence_number = 1;
+  QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(10);
+  SendDataPacket(sequence_number);
+  clock_.AdvanceTime(expected_rtt);
+
+  ExpectAck(sequence_number);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = sequence_number;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Infinite();
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+  EXPECT_EQ(expected_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->latest_rtt());
+}
+
+TEST_F(QuicSentPacketManagerTest, RttZeroDelta) {
+  // Expect that the RTT is the time between send and receive since the
+  // delta_time_largest_observed is zero.
+  QuicPacketSequenceNumber sequence_number = 1;
+  QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(10);
+  SendDataPacket(sequence_number);
+  clock_.AdvanceTime(expected_rtt);
+
+  ExpectAck(sequence_number);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = sequence_number;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+  EXPECT_EQ(expected_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->latest_rtt());
+}
+
+TEST_F(QuicSentPacketManagerTest, TailLossProbeTimeout) {
+  QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 2);
+
+  // Send 1 packet.
+  QuicPacketSequenceNumber sequence_number = 1;
+  SendDataPacket(sequence_number);
+
+  // The first tail loss probe retransmits 1 packet.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  manager_.MaybeRetransmitTailLossProbe();
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  RetransmitNextPacket(2);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  // The second tail loss probe retransmits 1 packet.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  manager_.MaybeRetransmitTailLossProbe();
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  RetransmitNextPacket(3);
+  EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  EXPECT_EQ(QuicTime::Delta::Infinite(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  // Ack the third and ensure the first two are still pending.
+  ExpectAck(3);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 3;
+  ack_frame.missing_packets.insert(1);
+  ack_frame.missing_packets.insert(2);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+
+  // Acking two more packets will lose both of them due to nacks.
+  ack_frame.largest_observed = 5;
+  QuicPacketSequenceNumber lost[] = { 1, 2 };
+  ExpectAcksAndLosses(false, nullptr, 0, lost, arraysize(lost));
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+  EXPECT_EQ(2u, stats_.tlp_count);
+  EXPECT_EQ(0u, stats_.rto_count);
+}
+
+TEST_F(QuicSentPacketManagerTest, TailLossProbeThenRTO) {
+  QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 2);
+
+  // Send 100 packets.
+  const size_t kNumSentPackets = 100;
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+
+  // The first tail loss probe retransmits 1 packet.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  manager_.MaybeRetransmitTailLossProbe();
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  RetransmitNextPacket(101);
+  EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  EXPECT_EQ(QuicTime::Delta::Infinite(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  // The second tail loss probe retransmits 1 packet.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(manager_.MaybeRetransmitTailLossProbe());
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  RetransmitNextPacket(102);
+  EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  EXPECT_EQ(QuicTime::Delta::Infinite(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+
+  // Advance the time enough to ensure all packets are RTO'd.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000));
+
+  // The final RTO abandons all of them.
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(2 * kDefaultTCPMSS));
+  manager_.OnRetransmissionTimeout();
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  EXPECT_EQ(2u, stats_.tlp_count);
+  EXPECT_EQ(1u, stats_.rto_count);
+}
+
+TEST_F(QuicSentPacketManagerTest, CryptoHandshakeTimeout) {
+  // Send 2 crypto packets and 3 data packets.
+  const size_t kNumSentCryptoPackets = 2;
+  for (size_t i = 1; i <= kNumSentCryptoPackets; ++i) {
+    SendCryptoPacket(i);
+  }
+  const size_t kNumSentDataPackets = 3;
+  for (size_t i = 1; i <= kNumSentDataPackets; ++i) {
+    SendDataPacket(kNumSentCryptoPackets + i);
+  }
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // The first retransmits 2 packets.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  RetransmitNextPacket(6);
+  RetransmitNextPacket(7);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // The second retransmits 2 packets.
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  RetransmitNextPacket(8);
+  RetransmitNextPacket(9);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Now ack the two crypto packets and the speculatively encrypted request,
+  // and ensure the first four crypto packets get abandoned, but not lost.
+  QuicPacketSequenceNumber acked[] = { 3, 4, 5, 8, 9 };
+  ExpectAcksAndLosses(true, acked, arraysize(acked), nullptr, 0);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 9;
+  ack_frame.missing_packets.insert(1);
+  ack_frame.missing_packets.insert(2);
+  ack_frame.missing_packets.insert(6);
+  ack_frame.missing_packets.insert(7);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+}
+
+TEST_F(QuicSentPacketManagerTest, CryptoHandshakeTimeoutVersionNegotiation) {
+  // Send 2 crypto packets and 3 data packets.
+  const size_t kNumSentCryptoPackets = 2;
+  for (size_t i = 1; i <= kNumSentCryptoPackets; ++i) {
+    SendCryptoPacket(i);
+  }
+  const size_t kNumSentDataPackets = 3;
+  for (size_t i = 1; i <= kNumSentDataPackets; ++i) {
+    SendDataPacket(kNumSentCryptoPackets + i);
+  }
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // The first retransmission timeout retransmits 2 crypto packets.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(6);
+  RetransmitNextPacket(7);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Now act like a version negotiation packet arrived, which would cause all
+  // unacked packets to be retransmitted.
+  manager_.RetransmitUnackedPackets(ALL_UNACKED_RETRANSMISSION);
+
+  // Ensure the first two pending packets are the crypto retransmits.
+  ASSERT_TRUE(manager_.HasPendingRetransmissions());
+  EXPECT_EQ(6u, manager_.NextPendingRetransmission().sequence_number);
+  RetransmitNextPacket(8);
+  EXPECT_EQ(7u, manager_.NextPendingRetransmission().sequence_number);
+  RetransmitNextPacket(9);
+
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  // Send 3 more data packets and ensure the least unacked is raised.
+  RetransmitNextPacket(10);
+  RetransmitNextPacket(11);
+  RetransmitNextPacket(12);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  EXPECT_EQ(8u, manager_.GetLeastUnacked());
+}
+
+TEST_F(QuicSentPacketManagerTest, CryptoHandshakeSpuriousRetransmission) {
+  // Send 1 crypto packet.
+  SendCryptoPacket(1);
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Retransmit the crypto packet as 2.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(2);
+
+  // Retransmit the crypto packet as 3.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(3);
+
+  // Now ack the second crypto packet, and ensure the first gets removed, but
+  // the third does not.
+  ExpectUpdatedRtt(2);
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.missing_packets.insert(1);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+  QuicPacketSequenceNumber unacked[] = { 3 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+}
+
+TEST_F(QuicSentPacketManagerTest, CryptoHandshakeTimeoutUnsentDataPacket) {
+  // Send 2 crypto packets and 1 data packet.
+  const size_t kNumSentCryptoPackets = 2;
+  for (size_t i = 1; i <= kNumSentCryptoPackets; ++i) {
+    SendCryptoPacket(i);
+  }
+  SendDataPacket(3);
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Retransmit 2 crypto packets, but not the serialized packet.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(4);
+  RetransmitNextPacket(5);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+}
+
+TEST_F(QuicSentPacketManagerTest,
+       CryptoHandshakeRetransmissionThenRetransmitAll) {
+  // Send 1 crypto packet.
+  SendCryptoPacket(1);
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Retransmit the crypto packet as 2.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(2);
+
+  // Now retransmit all the unacked packets, which occurs when there is a
+  // version negotiation.
+  manager_.RetransmitUnackedPackets(ALL_UNACKED_RETRANSMISSION);
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+}
+
+TEST_F(QuicSentPacketManagerTest,
+       CryptoHandshakeRetransmissionThenNeuterAndAck) {
+  // Send 1 crypto packet.
+  SendCryptoPacket(1);
+  EXPECT_TRUE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+
+  // Retransmit the crypto packet as 2.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(2);
+
+  // Retransmit the crypto packet as 3.
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(3);
+
+  // Now neuter all unacked unencrypted packets, which occurs when the
+  // connection goes forward secure.
+  manager_.NeuterUnencryptedPackets();
+  QuicPacketSequenceNumber unacked[] = { 1, 2, 3};
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasUnackedCryptoPackets(&manager_));
+  EXPECT_FALSE(QuicSentPacketManagerPeer::HasPendingPackets(&manager_));
+
+  // Ensure both packets get discarded when packet 2 is acked.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 3;
+  ack_frame.missing_packets.insert(1);
+  ack_frame.missing_packets.insert(2);
+  ExpectUpdatedRtt(3);
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+  VerifyUnackedPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicSentPacketManagerTest, ResetRecentMinRTTWithEmptyWindow) {
+  QuicTime::Delta min_rtt = QuicTime::Delta::FromMilliseconds(50);
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->UpdateRtt(
+      min_rtt, QuicTime::Delta::Zero(), QuicTime::Zero());
+  EXPECT_EQ(min_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->min_rtt());
+  EXPECT_EQ(min_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(
+                &manager_)->recent_min_rtt());
+
+  // Send two packets with no prior bytes in flight.
+  SendDataPacket(1);
+  SendDataPacket(2);
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(100));
+  // Ack two packets with 100ms RTT observations.
+  QuicAckFrame ack_frame;
+  ack_frame.delta_time_largest_observed = QuicTime::Delta::Zero();
+  ack_frame.largest_observed = 1;
+  ExpectAck(1);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  // First ack does not change recent min rtt.
+  EXPECT_EQ(min_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(
+                &manager_)->recent_min_rtt());
+
+  ack_frame.largest_observed = 2;
+  ExpectAck(2);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  EXPECT_EQ(min_rtt,
+            QuicSentPacketManagerPeer::GetRttStats(&manager_)->min_rtt());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(100),
+            QuicSentPacketManagerPeer::GetRttStats(
+                &manager_)->recent_min_rtt());
+}
+
+TEST_F(QuicSentPacketManagerTest, RetransmissionTimeout) {
+  // Send 100 packets and then ensure all are abandoned when the RTO fires.
+  const size_t kNumSentPackets = 100;
+  for (size_t i = 1; i <= kNumSentPackets; ++i) {
+    SendDataPacket(i);
+  }
+
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(2 * kDefaultTCPMSS));
+  EXPECT_FALSE(manager_.MaybeRetransmitTailLossProbe());
+  manager_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionTime) {
+  EXPECT_EQ(QuicTime::Zero(), manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionTimeCryptoHandshake) {
+  SendCryptoPacket(1);
+
+  // Check the min.
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->set_initial_rtt_us(
+      1 * base::Time::kMicrosecondsPerMillisecond);
+  EXPECT_EQ(clock_.Now().Add(QuicTime::Delta::FromMilliseconds(10)),
+            manager_.GetRetransmissionTime());
+
+  // Test with a standard smoothed RTT.
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->set_initial_rtt_us(
+      100 * base::Time::kMicrosecondsPerMillisecond);
+
+  QuicTime::Delta srtt = manager_.GetRttStats()->SmoothedRtt();
+  QuicTime expected_time = clock_.Now().Add(srtt.Multiply(1.5));
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+
+  // Retransmit the packet by invoking the retransmission timeout.
+  clock_.AdvanceTime(srtt.Multiply(1.5));
+  manager_.OnRetransmissionTimeout();
+  RetransmitNextPacket(2);
+
+  // The retransmission time should now be twice as far in the future.
+  expected_time = clock_.Now().Add(srtt.Multiply(2).Multiply(1.5));
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionTimeTailLossProbe) {
+  QuicSentPacketManagerPeer::SetMaxTailLossProbes(&manager_, 2);
+  SendDataPacket(1);
+  SendDataPacket(2);
+
+  // Check the min.
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->set_initial_rtt_us(
+      1 * base::Time::kMicrosecondsPerMillisecond);
+  EXPECT_EQ(clock_.Now().Add(QuicTime::Delta::FromMilliseconds(10)),
+            manager_.GetRetransmissionTime());
+
+  // Test with a standard smoothed RTT.
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->set_initial_rtt_us(
+      100 * base::Time::kMicrosecondsPerMillisecond);
+  QuicTime::Delta srtt = manager_.GetRttStats()->SmoothedRtt();
+  QuicTime::Delta expected_tlp_delay = srtt.Multiply(2);
+  QuicTime expected_time = clock_.Now().Add(expected_tlp_delay);
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+
+  // Retransmit the packet by invoking the retransmission timeout.
+  clock_.AdvanceTime(expected_tlp_delay);
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(QuicTime::Delta::Zero(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_TRUE(manager_.MaybeRetransmitTailLossProbe());
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+  RetransmitNextPacket(3);
+  EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  EXPECT_EQ(QuicTime::Delta::Infinite(),
+            manager_.TimeUntilSend(clock_.Now(), HAS_RETRANSMITTABLE_DATA));
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+
+  expected_time = clock_.Now().Add(expected_tlp_delay);
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionTimeRTO) {
+  QuicSentPacketManagerPeer::GetRttStats(&manager_)->UpdateRtt(
+      QuicTime::Delta::FromMilliseconds(100),
+      QuicTime::Delta::Zero(),
+      QuicTime::Zero());
+
+  SendDataPacket(1);
+  SendDataPacket(2);
+  SendDataPacket(3);
+  SendDataPacket(4);
+
+  QuicTime::Delta expected_rto_delay = QuicTime::Delta::FromMilliseconds(500);
+  EXPECT_CALL(*send_algorithm_, RetransmissionDelay())
+      .WillRepeatedly(Return(expected_rto_delay));
+  QuicTime expected_time = clock_.Now().Add(expected_rto_delay);
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+
+  // Retransmit the packet by invoking the retransmission timeout.
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(2 * kDefaultTCPMSS));
+  EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+  clock_.AdvanceTime(expected_rto_delay);
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(0u, QuicSentPacketManagerPeer::GetBytesInFlight(&manager_));
+  RetransmitNextPacket(5);
+  RetransmitNextPacket(6);
+  EXPECT_EQ(2 * kDefaultLength,
+            QuicSentPacketManagerPeer::GetBytesInFlight(&manager_));
+  EXPECT_TRUE(manager_.HasPendingRetransmissions());
+
+  // The delay should double the second time.
+  expected_time = clock_.Now().Add(expected_rto_delay).Add(expected_rto_delay);
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+
+  // Ack a packet and ensure the RTO goes back to the original value.
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.missing_packets.insert(1);
+  ExpectUpdatedRtt(2);
+  EXPECT_CALL(*send_algorithm_, RevertRetransmissionTimeout());
+  manager_.OnIncomingAck(ack_frame, clock_.ApproximateNow());
+  EXPECT_FALSE(manager_.HasPendingRetransmissions());
+  EXPECT_EQ(4 * kDefaultLength,
+            QuicSentPacketManagerPeer::GetBytesInFlight(&manager_));
+
+  // Wait 2RTTs from now for the RTO, since it's the max of the RTO time
+  // and the TLP time.  In production, there would always be two TLP's first.
+  expected_time = clock_.Now().Add(QuicTime::Delta::FromMilliseconds(200));
+  EXPECT_EQ(expected_time, manager_.GetRetransmissionTime());
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionDelayMin) {
+  SendDataPacket(1);
+  EXPECT_CALL(*send_algorithm_, RetransmissionDelay())
+      .WillRepeatedly(Return(QuicTime::Delta::FromMilliseconds(1)));
+  QuicTime::Delta delay = QuicTime::Delta::FromMilliseconds(200);
+
+  // If the delay is smaller than the min, ensure it exponentially backs off
+  // from the min.
+  for (int i = 0; i < 5; ++i) {
+    EXPECT_EQ(delay,
+              QuicSentPacketManagerPeer::GetRetransmissionDelay(&manager_));
+    delay = delay.Add(delay);
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillOnce(Return(2 * kDefaultTCPMSS));
+    EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+    manager_.OnRetransmissionTimeout();
+    RetransmitNextPacket(i + 2);
+  }
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionDelayMax) {
+  EXPECT_CALL(*send_algorithm_, RetransmissionDelay())
+      .WillOnce(Return(QuicTime::Delta::FromSeconds(500)));
+
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(60),
+            QuicSentPacketManagerPeer::GetRetransmissionDelay(&manager_));
+}
+
+TEST_F(QuicSentPacketManagerTest, GetTransmissionDelay) {
+  SendDataPacket(1);
+  QuicTime::Delta delay = QuicTime::Delta::FromMilliseconds(500);
+  EXPECT_CALL(*send_algorithm_, RetransmissionDelay())
+      .WillRepeatedly(Return(delay));
+
+  // Delay should back off exponentially.
+  for (int i = 0; i < 5; ++i) {
+    EXPECT_EQ(delay,
+              QuicSentPacketManagerPeer::GetRetransmissionDelay(&manager_));
+    delay = delay.Add(delay);
+    EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+    EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+        .WillOnce(Return(2 * kDefaultTCPMSS));
+    EXPECT_CALL(*send_algorithm_, OnRetransmissionTimeout(true));
+    manager_.OnRetransmissionTimeout();
+    RetransmitNextPacket(i + 2);
+  }
+}
+
+TEST_F(QuicSentPacketManagerTest, GetLossDelay) {
+  MockLossAlgorithm* loss_algorithm = new MockLossAlgorithm();
+  QuicSentPacketManagerPeer::SetLossAlgorithm(&manager_, loss_algorithm);
+
+  EXPECT_CALL(*loss_algorithm, GetLossTimeout())
+      .WillRepeatedly(Return(QuicTime::Zero()));
+  SendDataPacket(1);
+  SendDataPacket(2);
+
+  // Handle an ack which causes the loss algorithm to be evaluated and
+  // set the loss timeout.
+  ExpectAck(2);
+  EXPECT_CALL(*loss_algorithm, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  QuicAckFrame ack_frame;
+  ack_frame.largest_observed = 2;
+  ack_frame.missing_packets.insert(1);
+  manager_.OnIncomingAck(ack_frame, clock_.Now());
+
+  QuicTime timeout(clock_.Now().Add(QuicTime::Delta::FromMilliseconds(10)));
+  EXPECT_CALL(*loss_algorithm, GetLossTimeout())
+      .WillRepeatedly(Return(timeout));
+  EXPECT_EQ(timeout, manager_.GetRetransmissionTime());
+
+  // Fire the retransmission timeout and ensure the loss detection algorithm
+  // is invoked.
+  EXPECT_CALL(*loss_algorithm, DetectLostPackets(_, _, _, _))
+      .WillOnce(Return(SequenceNumberSet()));
+  manager_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicSentPacketManagerTest, NegotiateTimeLossDetectionFromOptions) {
+  EXPECT_EQ(kNack,
+            QuicSentPacketManagerPeer::GetLossAlgorithm(
+                &manager_)->GetLossDetectionType());
+
+  QuicConfig config;
+  QuicTagVector options;
+  options.push_back(kTIME);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(100 * kDefaultTCPMSS));
+  manager_.SetFromConfig(config);
+
+  EXPECT_EQ(kTime,
+            QuicSentPacketManagerPeer::GetLossAlgorithm(
+                &manager_)->GetLossDetectionType());
+}
+
+TEST_F(QuicSentPacketManagerTest, NegotiateCongestionControlFromOptions) {
+  QuicConfig config;
+  QuicTagVector options;
+
+  options.push_back(kRENO);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  manager_.SetFromConfig(config);
+  EXPECT_EQ(kReno, QuicSentPacketManagerPeer::GetCongestionControlAlgorithm(
+      manager_)->GetCongestionControlType());
+
+  // TODO(rtenneti): Enable the following code after BBR code is checked in.
+#if 0
+  options.clear();
+  options.push_back(kTBBR);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  manager_.SetFromConfig(config);
+  EXPECT_EQ(kBBR, QuicSentPacketManagerPeer::GetCongestionControlAlgorithm(
+      manager_)->GetCongestionControlType());
+#endif
+}
+
+TEST_F(QuicSentPacketManagerTest, NegotiateNumConnectionsFromOptions) {
+  QuicConfig config;
+  QuicTagVector options;
+
+  options.push_back(k1CON);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, SetNumEmulatedConnections(1));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(100 * kDefaultTCPMSS));
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  manager_.SetFromConfig(config);
+
+  QuicSentPacketManagerPeer::SetIsServer(&manager_, false);
+  QuicConfig client_config;
+  client_config.SetConnectionOptionsToSend(options);
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, SetNumEmulatedConnections(1));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(100 * kDefaultTCPMSS));
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  manager_.SetFromConfig(client_config);
+}
+
+TEST_F(QuicSentPacketManagerTest, NegotiatePacingFromOptions) {
+  EXPECT_FALSE(manager_.using_pacing());
+
+  QuicConfig config;
+  QuicTagVector options;
+  options.push_back(kPACE);
+  QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(100 * kDefaultTCPMSS));
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  manager_.SetFromConfig(config);
+
+  EXPECT_TRUE(manager_.using_pacing());
+}
+
+TEST_F(QuicSentPacketManagerTest, UseInitialRoundTripTimeToSend) {
+  uint32 initial_rtt_us = 325000;
+  EXPECT_NE(initial_rtt_us,
+            manager_.GetRttStats()->SmoothedRtt().ToMicroseconds());
+
+  QuicConfig config;
+  config.SetInitialRoundTripTimeUsToSend(initial_rtt_us);
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  EXPECT_CALL(*network_change_visitor_, OnCongestionWindowChange(_));
+  EXPECT_CALL(*send_algorithm_, GetCongestionWindow())
+      .WillOnce(Return(100 * kDefaultTCPMSS));
+  manager_.SetFromConfig(config);
+
+  EXPECT_EQ(initial_rtt_us,
+            manager_.GetRttStats()->SmoothedRtt().ToMicroseconds());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_server.cc b/net/quic/quic_server.cc
new file mode 100644
index 0000000..b8013c4
--- /dev/null
+++ b/net/quic/quic_server.cc
@@ -0,0 +1,176 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server.h"
+
+#include <string.h>
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_dispatcher.h"
+#include "net/quic/quic_in_memory_cache.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_packet_writer.h"
+#include "net/udp/udp_server_socket.h"
+
+namespace net {
+
+namespace {
+
+const char kSourceAddressTokenSecret[] = "secret";
+
+// Allocate some extra space so we can send an error if the client goes over
+// the limit.
+const int kReadBufferSize = 2 * kMaxPacketSize;
+
+const uint32 kServerInitialFlowControlWindow = 100 * kMaxPacketSize;
+
+} // namespace
+
+QuicServer::QuicServer(const QuicConfig& config,
+                       const QuicVersionVector& supported_versions)
+    : helper_(base::MessageLoop::current()->message_loop_proxy().get(),
+              &clock_,
+              QuicRandom::GetInstance()),
+      config_(config),
+      crypto_config_(kSourceAddressTokenSecret, QuicRandom::GetInstance()),
+      supported_versions_(supported_versions),
+      read_pending_(false),
+      synchronous_read_count_(0),
+      read_buffer_(new IOBufferWithSize(kReadBufferSize)),
+      weak_factory_(this) {
+  Initialize();
+}
+
+void QuicServer::Initialize() {
+  // Initialize the in memory cache now.
+  QuicInMemoryCache::GetInstance();
+
+  scoped_ptr<CryptoHandshakeMessage> scfg(
+      crypto_config_.AddDefaultConfig(helper_.GetRandomGenerator(),
+                                      helper_.GetClock(),
+                                      QuicCryptoServerConfig::ConfigOptions()));
+
+  config_.SetInitialCongestionWindowToSend(kServerInitialFlowControlWindow);
+}
+
+QuicServer::~QuicServer() {
+}
+
+int QuicServer::Listen(const IPEndPoint& address) {
+  scoped_ptr<UDPServerSocket> socket(
+      new UDPServerSocket(&net_log_, NetLog::Source()));
+
+  socket->AllowAddressReuse();
+
+  int rc = socket->Listen(address);
+  if (rc < 0) {
+    LOG(ERROR) << "Listen() failed: " << ErrorToString(rc);
+    return rc;
+  }
+
+  // These send and receive buffer sizes are sized for a single connection,
+  // because the default usage of QuicServer is as a test server with one or
+  // two clients.  Adjust higher for use with many clients.
+  rc = socket->SetReceiveBufferSize(TcpReceiver::kReceiveWindowTCP);
+  if (rc < 0) {
+    LOG(ERROR) << "SetReceiveBufferSize() failed: " << ErrorToString(rc);
+    return rc;
+  }
+
+  rc = socket->SetSendBufferSize(20 * kMaxPacketSize);
+  if (rc < 0) {
+    LOG(ERROR) << "SetSendBufferSize() failed: " << ErrorToString(rc);
+    return rc;
+  }
+
+  rc = socket->GetLocalAddress(&server_address_);
+  if (rc < 0) {
+    LOG(ERROR) << "GetLocalAddress() failed: " << ErrorToString(rc);
+    return rc;
+  }
+
+  DVLOG(1) << "Listening on " << server_address_.ToString();
+
+  socket_.swap(socket);
+
+  dispatcher_.reset(
+      new QuicDispatcher(config_,
+                         crypto_config_,
+                         supported_versions_,
+                         new QuicDispatcher::DefaultPacketWriterFactory(),
+                         &helper_));
+  QuicServerPacketWriter* writer = new QuicServerPacketWriter(
+      socket_.get(),
+      dispatcher_.get());
+  dispatcher_->Initialize(writer);
+
+  StartReading();
+
+  return OK;
+}
+
+void QuicServer::Shutdown() {
+  // Before we shut down the epoll server, give all active sessions a chance to
+  // notify clients that they're closing.
+  dispatcher_->Shutdown();
+
+  socket_->Close();
+  socket_.reset();
+}
+
+void QuicServer::StartReading() {
+  if (read_pending_) {
+    return;
+  }
+  read_pending_ = true;
+
+  int result = socket_->RecvFrom(
+      read_buffer_.get(),
+      read_buffer_->size(),
+      &client_address_,
+      base::Bind(&QuicServer::OnReadComplete, base::Unretained(this)));
+
+  if (result == ERR_IO_PENDING) {
+    synchronous_read_count_ = 0;
+    return;
+  }
+
+  if (++synchronous_read_count_ > 32) {
+    synchronous_read_count_ = 0;
+    // Schedule the processing through the message loop to 1) prevent infinite
+    // recursion and 2) avoid blocking the thread for too long.
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&QuicServer::OnReadComplete,
+                   weak_factory_.GetWeakPtr(),
+                   result));
+  } else {
+    OnReadComplete(result);
+  }
+}
+
+void QuicServer::OnReadComplete(int result) {
+  read_pending_ = false;
+  if (result == 0)
+    result = ERR_CONNECTION_CLOSED;
+
+  if (result < 0) {
+    LOG(ERROR) << "QuicServer read failed: " << ErrorToString(result);
+    Shutdown();
+    return;
+  }
+
+  QuicEncryptedPacket packet(read_buffer_->data(), result, false);
+  dispatcher_->ProcessPacket(server_address_, client_address_, packet);
+
+  StartReading();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_server.h b/net/quic/quic_server.h
new file mode 100644
index 0000000..d53963e
--- /dev/null
+++ b/net/quic/quic_server.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A toy server, which listens on a specified address for QUIC traffic and
+// handles incoming responses.
+
+#ifndef NET_QUIC_QUIC_SERVER_H_
+#define NET_QUIC_QUIC_SERVER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_connection_helper.h"
+
+namespace net {
+
+
+namespace test {
+class QuicServerPeer;
+}  // namespace test
+
+class QuicDispatcher;
+class UDPServerSocket;
+
+class QuicServer {
+ public:
+  QuicServer(const QuicConfig& config,
+             const QuicVersionVector& supported_versions);
+
+  virtual ~QuicServer();
+
+  // Start listening on the specified address. Returns an error code.
+  int Listen(const IPEndPoint& address);
+
+  // Server deletion is imminent. Start cleaning up.
+  void Shutdown();
+
+  // Start reading on the socket. On asynchronous reads, this registers
+  // OnReadComplete as the callback, which will then call StartReading again.
+  void StartReading();
+
+  // Called on reads that complete asynchronously. Dispatches the packet and
+  // continues the read loop.
+  void OnReadComplete(int result);
+
+  void SetStrikeRegisterNoStartupPeriod() {
+    crypto_config_.set_strike_register_no_startup_period();
+  }
+
+  // SetProofSource sets the ProofSource that will be used to verify the
+  // server's certificate, and takes ownership of |source|.
+  void SetProofSource(ProofSource* source) {
+    crypto_config_.SetProofSource(source);
+  }
+
+ private:
+  friend class net::test::QuicServerPeer;
+
+  // Initialize the internal state of the server.
+  void Initialize();
+
+  // Accepts data from the framer and demuxes clients to sessions.
+  scoped_ptr<QuicDispatcher> dispatcher_;
+
+  // Used by the helper_ to time alarms.
+  QuicClock clock_;
+
+  // Used to manage the message loop.
+  QuicConnectionHelper helper_;
+
+  // Listening socket. Also used for outbound client communication.
+  scoped_ptr<UDPServerSocket> socket_;
+
+  // config_ contains non-crypto parameters that are negotiated in the crypto
+  // handshake.
+  QuicConfig config_;
+  // crypto_config_ contains crypto parameters for the handshake.
+  QuicCryptoServerConfig crypto_config_;
+
+  // This vector contains QUIC versions which we currently support.
+  // This should be ordered such that the highest supported version is the first
+  // element, with subsequent elements in descending order (versions can be
+  // skipped as necessary).
+  QuicVersionVector supported_versions_;
+
+  // The address that the server listens on.
+  IPEndPoint server_address_;
+
+  // Keeps track of whether a read is currently in flight, after which
+  // OnReadComplete will be called.
+  bool read_pending_;
+
+  // The number of iterations of the read loop that have completed synchronously
+  // and without posting a new task to the message loop.
+  int synchronous_read_count_;
+
+  // The target buffer of the current read.
+  scoped_refptr<IOBufferWithSize> read_buffer_;
+
+  // The source address of the current read.
+  IPEndPoint client_address_;
+
+  // The log to use for the socket.
+  NetLog net_log_;
+
+  base::WeakPtrFactory<QuicServer> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicServer);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SERVER_H_
diff --git a/net/quic/quic_server_bin.cc b/net/quic/quic_server_bin.cc
new file mode 100644
index 0000000..63b85a7
--- /dev/null
+++ b/net/quic/quic_server_bin.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A binary wrapper for QuicServer.  It listens forever on --port
+// (default 6121) until it's killed or ctrl-cd to death.
+
+#include <iostream>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_in_memory_cache.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server.h"
+
+// The port the quic server will listen on.
+int32 FLAGS_port = 6121;
+
+int main(int argc, char *argv[]) {
+  base::CommandLine::Init(argc, argv);
+  base::CommandLine* line = base::CommandLine::ForCurrentProcess();
+
+  logging::LoggingSettings settings;
+  settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  CHECK(logging::InitLogging(settings));
+
+  if (line->HasSwitch("h") || line->HasSwitch("help")) {
+    const char* help_str =
+        "Usage: quic_server [options]\n"
+        "\n"
+        "Options:\n"
+        "-h, --help                  show this help message and exit\n"
+        "--port=<port>               specify the port to listen on\n"
+        "--quic_in_memory_cache_dir  directory containing response data\n"
+        "                            to load\n";
+    std::cout << help_str;
+    exit(0);
+  }
+
+  if (line->HasSwitch("quic_in_memory_cache_dir")) {
+    net::g_quic_in_memory_cache_dir =
+        line->GetSwitchValueNative("quic_in_memory_cache_dir");
+  }
+
+  if (line->HasSwitch("port")) {
+    int port;
+    if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) {
+      FLAGS_port = port;
+    }
+  }
+
+  base::AtExitManager exit_manager;
+
+  base::MessageLoopForIO message_loop;
+
+  net::IPAddressNumber ip;
+  CHECK(net::ParseIPLiteralToNumber("::", &ip));
+
+  net::QuicConfig config;
+  config.SetDefaults();
+
+  net::QuicServer server(config, net::QuicSupportedVersions());
+
+  int rc = server.Listen(net::IPEndPoint(ip, FLAGS_port));
+  if (rc < 0) {
+    return 1;
+  }
+
+  base::RunLoop().Run();
+
+  return 0;
+}
diff --git a/net/quic/quic_server_id.cc b/net/quic/quic_server_id.cc
new file mode 100644
index 0000000..ff87099
--- /dev/null
+++ b/net/quic/quic_server_id.cc
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server_id.h"
+
+using std::string;
+
+namespace net {
+
+QuicServerId::QuicServerId() {}
+
+QuicServerId::QuicServerId(const HostPortPair& host_port_pair,
+                           bool is_https,
+                           PrivacyMode privacy_mode)
+    : host_port_pair_(host_port_pair),
+      is_https_(is_https),
+      privacy_mode_(privacy_mode) {}
+
+QuicServerId::QuicServerId(const string& host,
+                           uint16 port,
+                           bool is_https)
+    : host_port_pair_(host, port),
+      is_https_(is_https),
+      privacy_mode_(PRIVACY_MODE_DISABLED) {}
+
+QuicServerId::QuicServerId(const string& host,
+                           uint16 port,
+                           bool is_https,
+                           PrivacyMode privacy_mode)
+    : host_port_pair_(host, port),
+      is_https_(is_https),
+      privacy_mode_(privacy_mode) {}
+
+QuicServerId::~QuicServerId() {}
+
+bool QuicServerId::operator<(const QuicServerId& other) const {
+  if (!host_port_pair_.Equals(other.host_port_pair_)) {
+    return host_port_pair_ < other.host_port_pair_;
+  }
+  if (is_https_ != other.is_https_) {
+    return is_https_ < other.is_https_;
+  }
+  return privacy_mode_ < other.privacy_mode_;
+}
+
+bool QuicServerId::operator==(const QuicServerId& other) const {
+  return is_https_ == other.is_https_ &&
+      privacy_mode_ == other.privacy_mode_ &&
+      host_port_pair_.Equals(other.host_port_pair_);
+}
+
+string QuicServerId::ToString() const {
+  return (is_https_ ? "https://" : "http://") + host_port_pair_.ToString() +
+      (privacy_mode_ == PRIVACY_MODE_ENABLED ? "/private" : "");
+}
+
+}  // namespace net
diff --git a/net/quic/quic_server_id.h b/net/quic/quic_server_id.h
new file mode 100644
index 0000000..6d01670
--- /dev/null
+++ b/net/quic/quic_server_id.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SERVER_ID_H_
+#define NET_QUIC_QUIC_SERVER_ID_H_
+
+#include <string>
+
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/base/privacy_mode.h"
+
+namespace net {
+
+// The id used to identify sessions. Includes the hostname, port, scheme and
+// privacy_mode.
+class NET_EXPORT_PRIVATE QuicServerId {
+ public:
+  QuicServerId();
+  QuicServerId(const HostPortPair& host_port_pair,
+               bool is_https,
+               PrivacyMode privacy_mode);
+  QuicServerId(const std::string& host,
+               uint16 port,
+               bool is_https);
+  QuicServerId(const std::string& host,
+               uint16 port,
+               bool is_https,
+               PrivacyMode privacy_mode);
+  ~QuicServerId();
+
+  // Needed to be an element of std::set.
+  bool operator<(const QuicServerId& other) const;
+  bool operator==(const QuicServerId& other) const;
+
+  // ToString() will convert the QuicServerId to "scheme:hostname:port" or
+  // "scheme:hostname:port/private". "scheme" would either be "http" or "https"
+  // based on |is_https|.
+  std::string ToString() const;
+
+  const HostPortPair& host_port_pair() const { return host_port_pair_; }
+
+  const std::string& host() const { return host_port_pair_.host(); }
+
+  uint16 port() const { return host_port_pair_.port(); }
+
+  bool is_https() const { return is_https_; }
+
+  PrivacyMode privacy_mode() const { return privacy_mode_; }
+
+ private:
+  HostPortPair host_port_pair_;
+  bool is_https_;
+  PrivacyMode privacy_mode_;
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SERVER_ID_H_
diff --git a/net/quic/quic_server_id_test.cc b/net/quic/quic_server_id_test.cc
new file mode 100644
index 0000000..1e08b30
--- /dev/null
+++ b/net/quic/quic_server_id_test.cc
@@ -0,0 +1,287 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server_id.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+
+namespace {
+
+TEST(QuicServerIdTest, ToString) {
+  HostPortPair google_host_port_pair("google.com", 10);
+
+  QuicServerId google_http_server_id(google_host_port_pair, false,
+                                     PRIVACY_MODE_DISABLED);
+  string google_http_server_id_str = google_http_server_id.ToString();
+  EXPECT_EQ("http://google.com:10", google_http_server_id_str);
+
+  QuicServerId google_https_server_id(google_host_port_pair, true,
+                                      PRIVACY_MODE_DISABLED);
+  string google_https_server_id_str = google_https_server_id.ToString();
+  EXPECT_EQ("https://google.com:10", google_https_server_id_str);
+
+  QuicServerId private_http_server_id(google_host_port_pair, false,
+                                      PRIVACY_MODE_ENABLED);
+  string private_http_server_id_str = private_http_server_id.ToString();
+  EXPECT_EQ("http://google.com:10/private", private_http_server_id_str);
+
+  QuicServerId private_https_server_id(google_host_port_pair, true,
+                                       PRIVACY_MODE_ENABLED);
+  string private_https_server_id_str = private_https_server_id.ToString();
+  EXPECT_EQ("https://google.com:10/private", private_https_server_id_str);
+}
+
+TEST(QuicServerIdTest, LessThan) {
+  QuicServerId a_10_http(HostPortPair("a.com", 10), false,
+                         PRIVACY_MODE_DISABLED);
+  QuicServerId a_10_https(HostPortPair("a.com", 10), true,
+                          PRIVACY_MODE_DISABLED);
+  QuicServerId a_11_http(HostPortPair("a.com", 11), false,
+                         PRIVACY_MODE_DISABLED);
+  QuicServerId a_11_https(HostPortPair("a.com", 11), true,
+                          PRIVACY_MODE_DISABLED);
+  QuicServerId b_10_http(HostPortPair("b.com", 10), false,
+                         PRIVACY_MODE_DISABLED);
+  QuicServerId b_10_https(HostPortPair("b.com", 10), true,
+                          PRIVACY_MODE_DISABLED);
+  QuicServerId b_11_http(HostPortPair("b.com", 11), false,
+                         PRIVACY_MODE_DISABLED);
+  QuicServerId b_11_https(HostPortPair("b.com", 11), true,
+                          PRIVACY_MODE_DISABLED);
+
+  QuicServerId a_10_http_private(HostPortPair("a.com", 10), false,
+                                 PRIVACY_MODE_ENABLED);
+  QuicServerId a_10_https_private(HostPortPair("a.com", 10), true,
+                                  PRIVACY_MODE_ENABLED);
+  QuicServerId a_11_http_private(HostPortPair("a.com", 11), false,
+                                 PRIVACY_MODE_ENABLED);
+  QuicServerId a_11_https_private(HostPortPair("a.com", 11), true,
+                                  PRIVACY_MODE_ENABLED);
+  QuicServerId b_10_http_private(HostPortPair("b.com", 10), false,
+                                 PRIVACY_MODE_ENABLED);
+  QuicServerId b_10_https_private(HostPortPair("b.com", 10), true,
+                                  PRIVACY_MODE_ENABLED);
+  QuicServerId b_11_http_private(HostPortPair("b.com", 11), false,
+                                 PRIVACY_MODE_ENABLED);
+  QuicServerId b_11_https_private(HostPortPair("b.com", 11), true,
+                                  PRIVACY_MODE_ENABLED);
+
+  // Test combinations of host, port, https and privacy being same on left and
+  // right side of less than.
+  EXPECT_FALSE(a_10_http  < a_10_http);
+  EXPECT_TRUE(a_10_http   < a_10_https);
+  EXPECT_FALSE(a_10_https < a_10_http);
+  EXPECT_FALSE(a_10_https < a_10_https);
+
+  EXPECT_TRUE(a_10_http   < a_10_http_private);
+  EXPECT_TRUE(a_10_http   < a_10_https_private);
+  EXPECT_FALSE(a_10_https < a_10_http_private);
+  EXPECT_TRUE(a_10_https  < a_10_https_private);
+
+  EXPECT_FALSE(a_10_http_private  < a_10_http);
+  EXPECT_TRUE(a_10_http_private   < a_10_https);
+  EXPECT_FALSE(a_10_https_private < a_10_http);
+  EXPECT_FALSE(a_10_https_private < a_10_https);
+
+  EXPECT_FALSE(a_10_http_private  < a_10_http_private);
+  EXPECT_TRUE(a_10_http_private   < a_10_https_private);
+  EXPECT_FALSE(a_10_https_private < a_10_http_private);
+  EXPECT_FALSE(a_10_https_private < a_10_https_private);
+
+  // Test with either host, port or https being different on left and right side
+  // of less than.
+  PrivacyMode left_privacy;
+  PrivacyMode right_privacy;
+  for (int i = 0; i < 4; i++) {
+    left_privacy = static_cast<PrivacyMode>(i / 2);
+    right_privacy = static_cast<PrivacyMode>(i % 2);
+    QuicServerId a_10_http_left_private(HostPortPair("a.com", 10), false,
+                                        left_privacy);
+    QuicServerId a_10_http_right_private(HostPortPair("a.com", 10), false,
+                                         right_privacy);
+    QuicServerId a_10_https_left_private(HostPortPair("a.com", 10), true,
+                                         left_privacy);
+    QuicServerId a_10_https_right_private(HostPortPair("a.com", 10), true,
+                                          right_privacy);
+    QuicServerId a_11_http_left_private(HostPortPair("a.com", 11), false,
+                                        left_privacy);
+    QuicServerId a_11_http_right_private(HostPortPair("a.com", 11), false,
+                                         right_privacy);
+    QuicServerId a_11_https_left_private(HostPortPair("a.com", 11), true,
+                                         left_privacy);
+    QuicServerId a_11_https_right_private(HostPortPair("a.com", 11), true,
+                                          right_privacy);
+
+    QuicServerId b_10_http_left_private(HostPortPair("b.com", 10), false,
+                                        left_privacy);
+    QuicServerId b_10_http_right_private(HostPortPair("b.com", 10), false,
+                                         right_privacy);
+    QuicServerId b_10_https_left_private(HostPortPair("b.com", 10), true,
+                                         left_privacy);
+    QuicServerId b_10_https_right_private(HostPortPair("b.com", 10), true,
+                                          right_privacy);
+    QuicServerId b_11_http_left_private(HostPortPair("b.com", 11), false,
+                                        left_privacy);
+    QuicServerId b_11_http_right_private(HostPortPair("b.com", 11), false,
+                                         right_privacy);
+    QuicServerId b_11_https_left_private(HostPortPair("b.com", 11), true,
+                                         left_privacy);
+    QuicServerId b_11_https_right_private(HostPortPair("b.com", 11), true,
+                                          right_privacy);
+
+    EXPECT_TRUE(a_10_http_left_private  < a_11_http_right_private);
+    EXPECT_TRUE(a_10_http_left_private  < a_11_https_right_private);
+    EXPECT_TRUE(a_10_https_left_private < a_11_http_right_private);
+    EXPECT_TRUE(a_10_https_left_private < a_11_https_right_private);
+
+    EXPECT_TRUE(a_10_http_left_private  < b_10_http_right_private);
+    EXPECT_TRUE(a_10_http_left_private  < b_10_https_right_private);
+    EXPECT_TRUE(a_10_https_left_private < b_10_http_right_private);
+    EXPECT_TRUE(a_10_https_left_private < b_10_https_right_private);
+
+    EXPECT_TRUE(a_10_http_left_private  < b_11_http_right_private);
+    EXPECT_TRUE(a_10_http_left_private  < b_11_https_right_private);
+    EXPECT_TRUE(a_10_https_left_private < b_11_http_right_private);
+    EXPECT_TRUE(a_10_https_left_private < b_11_https_right_private);
+
+    EXPECT_FALSE(a_11_http_left_private  < a_10_http_right_private);
+    EXPECT_FALSE(a_11_http_left_private  < a_10_https_right_private);
+    EXPECT_FALSE(a_11_https_left_private < a_10_http_right_private);
+    EXPECT_FALSE(a_11_https_left_private < a_10_https_right_private);
+
+    EXPECT_FALSE(a_11_http_left_private  < b_10_http_right_private);
+    EXPECT_FALSE(a_11_http_left_private  < b_10_https_right_private);
+    EXPECT_FALSE(a_11_https_left_private < b_10_http_right_private);
+    EXPECT_FALSE(a_11_https_left_private < b_10_https_right_private);
+
+    EXPECT_TRUE(a_11_http_left_private  < b_11_http_right_private);
+    EXPECT_TRUE(a_11_http_left_private  < b_11_https_right_private);
+    EXPECT_TRUE(a_11_https_left_private < b_11_http_right_private);
+    EXPECT_TRUE(a_11_https_left_private < b_11_https_right_private);
+
+    EXPECT_FALSE(b_10_http_left_private  < a_10_http_right_private);
+    EXPECT_FALSE(b_10_http_left_private  < a_10_https_right_private);
+    EXPECT_FALSE(b_10_https_left_private < a_10_http_right_private);
+    EXPECT_FALSE(b_10_https_left_private < a_10_https_right_private);
+
+    EXPECT_TRUE(b_10_http_left_private  < a_11_http_right_private);
+    EXPECT_TRUE(b_10_http_left_private  < a_11_https_right_private);
+    EXPECT_TRUE(b_10_https_left_private < a_11_http_right_private);
+    EXPECT_TRUE(b_10_https_left_private < a_11_https_right_private);
+
+    EXPECT_TRUE(b_10_http_left_private  < b_11_http_right_private);
+    EXPECT_TRUE(b_10_http_left_private  < b_11_https_right_private);
+    EXPECT_TRUE(b_10_https_left_private < b_11_http_right_private);
+    EXPECT_TRUE(b_10_https_left_private < b_11_https_right_private);
+
+    EXPECT_FALSE(b_11_http_left_private  < a_10_http_right_private);
+    EXPECT_FALSE(b_11_http_left_private  < a_10_https_right_private);
+    EXPECT_FALSE(b_11_https_left_private < a_10_http_right_private);
+    EXPECT_FALSE(b_11_https_left_private < a_10_https_right_private);
+
+    EXPECT_FALSE(b_11_http_left_private  < a_11_http_right_private);
+    EXPECT_FALSE(b_11_http_left_private  < a_11_https_right_private);
+    EXPECT_FALSE(b_11_https_left_private < a_11_http_right_private);
+    EXPECT_FALSE(b_11_https_left_private < a_11_https_right_private);
+
+    EXPECT_FALSE(b_11_http_left_private  < b_10_http_right_private);
+    EXPECT_FALSE(b_11_http_left_private  < b_10_https_right_private);
+    EXPECT_FALSE(b_11_https_left_private < b_10_http_right_private);
+    EXPECT_FALSE(b_11_https_left_private < b_10_https_right_private);
+  }
+}
+
+TEST(QuicServerIdTest, Equals) {
+  PrivacyMode left_privacy;
+  PrivacyMode right_privacy;
+  for (int i = 0; i < 2; i++) {
+    left_privacy = right_privacy = static_cast<PrivacyMode>(i);
+    QuicServerId a_10_http_right_private(HostPortPair("a.com", 10), false,
+                                         right_privacy);
+    QuicServerId a_10_https_right_private(HostPortPair("a.com", 10), true,
+                                          right_privacy);
+    QuicServerId a_11_http_right_private(HostPortPair("a.com", 11), false,
+                                         right_privacy);
+    QuicServerId a_11_https_right_private(HostPortPair("a.com", 11), true,
+                                          right_privacy);
+    QuicServerId b_10_http_right_private(HostPortPair("b.com", 10), false,
+                                         right_privacy);
+    QuicServerId b_10_https_right_private(HostPortPair("b.com", 10), true,
+                                          right_privacy);
+    QuicServerId b_11_http_right_private(HostPortPair("b.com", 11), false,
+                                         right_privacy);
+    QuicServerId b_11_https_right_private(HostPortPair("b.com", 11), true,
+                                          right_privacy);
+
+    QuicServerId new_a_10_http_left_private(HostPortPair("a.com", 10), false,
+                                            left_privacy);
+    QuicServerId new_a_10_https_left_private(HostPortPair("a.com", 10), true,
+                                             left_privacy);
+    QuicServerId new_a_11_http_left_private(HostPortPair("a.com", 11), false,
+                                            left_privacy);
+    QuicServerId new_a_11_https_left_private(HostPortPair("a.com", 11), true,
+                                             left_privacy);
+    QuicServerId new_b_10_http_left_private(HostPortPair("b.com", 10), false,
+                                            left_privacy);
+    QuicServerId new_b_10_https_left_private(HostPortPair("b.com", 10), true,
+                                             left_privacy);
+    QuicServerId new_b_11_http_left_private(HostPortPair("b.com", 11), false,
+                                            left_privacy);
+    QuicServerId new_b_11_https_left_private(HostPortPair("b.com", 11), true,
+                                             left_privacy);
+
+    EXPECT_EQ(new_a_10_http_left_private,  a_10_http_right_private);
+    EXPECT_EQ(new_a_10_https_left_private, a_10_https_right_private);
+    EXPECT_EQ(new_a_11_http_left_private,  a_11_http_right_private);
+    EXPECT_EQ(new_a_11_https_left_private, a_11_https_right_private);
+    EXPECT_EQ(new_b_10_http_left_private,  b_10_http_right_private);
+    EXPECT_EQ(new_b_10_https_left_private, b_10_https_right_private);
+    EXPECT_EQ(new_b_11_http_left_private,  b_11_http_right_private);
+    EXPECT_EQ(new_b_11_https_left_private, b_11_https_right_private);
+  }
+
+  for (int i = 0; i < 2; i++) {
+    right_privacy = static_cast<PrivacyMode>(i);
+    QuicServerId a_10_http_right_private(HostPortPair("a.com", 10), false,
+                                         right_privacy);
+    QuicServerId a_10_https_right_private(HostPortPair("a.com", 10), true,
+                                          right_privacy);
+    QuicServerId a_11_http_right_private(HostPortPair("a.com", 11), false,
+                                         right_privacy);
+    QuicServerId a_11_https_right_private(HostPortPair("a.com", 11), true,
+                                          right_privacy);
+    QuicServerId b_10_http_right_private(HostPortPair("b.com", 10), false,
+                                         right_privacy);
+    QuicServerId b_10_https_right_private(HostPortPair("b.com", 10), true,
+                                          right_privacy);
+    QuicServerId b_11_http_right_private(HostPortPair("b.com", 11), false,
+                                         right_privacy);
+    QuicServerId b_11_https_right_private(HostPortPair("b.com", 11), true,
+                                          right_privacy);
+
+    QuicServerId new_a_10_http_left_private(HostPortPair("a.com", 10), false,
+                                            PRIVACY_MODE_DISABLED);
+
+    EXPECT_FALSE(new_a_10_http_left_private == a_10_https_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == a_11_http_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == b_10_http_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == a_11_https_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == b_10_https_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == b_11_http_right_private);
+    EXPECT_FALSE(new_a_10_http_left_private == b_11_https_right_private);
+  }
+  QuicServerId a_10_http_private(HostPortPair("a.com", 10), false,
+                                 PRIVACY_MODE_ENABLED);
+  QuicServerId new_a_10_http_no_private(HostPortPair("a.com", 10), false,
+                                        PRIVACY_MODE_DISABLED);
+  EXPECT_FALSE(new_a_10_http_no_private == a_10_http_private);
+}
+
+}  // namespace
+
+}  // namespace net
diff --git a/net/quic/quic_server_packet_writer.cc b/net/quic/quic_server_packet_writer.cc
new file mode 100644
index 0000000..38d93d9
--- /dev/null
+++ b/net/quic/quic_server_packet_writer.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server_packet_writer.h"
+
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/udp/udp_server_socket.h"
+
+namespace net {
+
+QuicServerPacketWriter::QuicServerPacketWriter(
+    UDPServerSocket* socket,
+    QuicBlockedWriterInterface* blocked_writer)
+    : socket_(socket),
+      blocked_writer_(blocked_writer),
+      write_blocked_(false),
+      weak_factory_(this) {
+}
+
+QuicServerPacketWriter::~QuicServerPacketWriter() {
+}
+
+WriteResult QuicServerPacketWriter::WritePacketWithCallback(
+    const char* buffer,
+    size_t buf_len,
+    const IPAddressNumber& self_address,
+    const IPEndPoint& peer_address,
+    WriteCallback callback) {
+  DCHECK(callback_.is_null());
+  callback_ = callback;
+  WriteResult result = WritePacket(buffer, buf_len, self_address, peer_address);
+  if (result.status != WRITE_STATUS_BLOCKED) {
+    callback_.Reset();
+  }
+  return result;
+}
+
+void QuicServerPacketWriter::OnWriteComplete(int rv) {
+  DCHECK_NE(rv, ERR_IO_PENDING);
+  write_blocked_ = false;
+  WriteResult result(rv < 0 ? WRITE_STATUS_ERROR : WRITE_STATUS_OK, rv);
+  base::ResetAndReturn(&callback_).Run(result);
+  blocked_writer_->OnCanWrite();
+}
+
+bool QuicServerPacketWriter::IsWriteBlockedDataBuffered() const {
+  // UDPServerSocket::SendTo buffers the data until the Write is permitted.
+  return true;
+}
+
+bool QuicServerPacketWriter::IsWriteBlocked() const {
+  return write_blocked_;
+}
+
+void QuicServerPacketWriter::SetWritable() {
+  write_blocked_ = false;
+}
+
+WriteResult QuicServerPacketWriter::WritePacket(
+    const char* buffer,
+    size_t buf_len,
+    const IPAddressNumber& self_address,
+    const IPEndPoint& peer_address) {
+  scoped_refptr<StringIOBuffer> buf(
+      new StringIOBuffer(std::string(buffer, buf_len)));
+  DCHECK(!IsWriteBlocked());
+  DCHECK(!callback_.is_null());
+  int rv;
+  if (buf_len <= static_cast<size_t>(std::numeric_limits<int>::max())) {
+    rv = socket_->SendTo(buf.get(),
+                         static_cast<int>(buf_len),
+                         peer_address,
+                         base::Bind(&QuicServerPacketWriter::OnWriteComplete,
+                                    weak_factory_.GetWeakPtr()));
+  } else {
+    rv = ERR_MSG_TOO_BIG;
+  }
+  WriteStatus status = WRITE_STATUS_OK;
+  if (rv < 0) {
+    if (rv != ERR_IO_PENDING) {
+      UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.WriteError", -rv);
+      status = WRITE_STATUS_ERROR;
+    } else {
+      status = WRITE_STATUS_BLOCKED;
+      write_blocked_ = true;
+    }
+  }
+  return WriteResult(status, rv);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_server_packet_writer.h b/net/quic/quic_server_packet_writer.h
new file mode 100644
index 0000000..417ea8d
--- /dev/null
+++ b/net/quic/quic_server_packet_writer.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SERVER_PACKET_WRITER_H_
+#define NET_QUIC_QUIC_SERVER_PACKET_WRITER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_packet_writer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicBlockedWriterInterface;
+class UDPServerSocket;
+struct WriteResult;
+
+// Chrome specific packet writer which uses a UDPServerSocket for writing
+// data.
+class QuicServerPacketWriter : public QuicPacketWriter {
+ public:
+  typedef base::Callback<void(WriteResult)> WriteCallback;
+
+  QuicServerPacketWriter(UDPServerSocket* socket,
+                         QuicBlockedWriterInterface* blocked_writer);
+  virtual ~QuicServerPacketWriter();
+
+  // Use this method to write packets rather than WritePacket:
+  // QuicServerPacketWriter requires a callback to exist for every write, which
+  // will be called once the write completes.
+  virtual WriteResult WritePacketWithCallback(
+      const char* buffer,
+      size_t buf_len,
+      const IPAddressNumber& self_address,
+      const IPEndPoint& peer_address,
+      WriteCallback callback);
+
+  void OnWriteComplete(int rv);
+
+  // QuicPacketWriter implementation:
+  virtual bool IsWriteBlockedDataBuffered() const OVERRIDE;
+  virtual bool IsWriteBlocked() const OVERRIDE;
+  virtual void SetWritable() OVERRIDE;
+
+ protected:
+  // Do not call WritePacket on its own -- use WritePacketWithCallback
+  virtual WriteResult WritePacket(const char* buffer,
+                                  size_t buf_len,
+                                  const IPAddressNumber& self_address,
+                                  const IPEndPoint& peer_address) OVERRIDE;
+ private:
+  UDPServerSocket* socket_;
+
+  // To be notified after every successful asynchronous write.
+  QuicBlockedWriterInterface* blocked_writer_;
+
+  // To call once the write completes.
+  WriteCallback callback_;
+
+  // Whether a write is currently in flight.
+  bool write_blocked_;
+
+  base::WeakPtrFactory<QuicServerPacketWriter> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicServerPacketWriter);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SERVER_PACKET_WRITER_H_
diff --git a/net/quic/quic_server_session.cc b/net/quic/quic_server_session.cc
new file mode 100644
index 0000000..bacd59e
--- /dev/null
+++ b/net/quic/quic_server_session.cc
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server_session.h"
+
+#include "base/logging.h"
+#include "net/quic/crypto/source_address_token.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_spdy_server_stream.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+QuicServerSession::QuicServerSession(
+    const QuicConfig& config,
+    QuicConnection* connection,
+    QuicServerSessionVisitor* visitor)
+    : QuicSession(connection, config),
+      visitor_(visitor),
+      bandwidth_estimate_sent_to_client_(QuicBandwidth::Zero()),
+      last_server_config_update_time_(QuicTime::Zero()) {}
+
+QuicServerSession::~QuicServerSession() {}
+
+void QuicServerSession::InitializeSession(
+    const QuicCryptoServerConfig& crypto_config) {
+  QuicSession::InitializeSession();
+  crypto_stream_.reset(CreateQuicCryptoServerStream(crypto_config));
+}
+
+QuicCryptoServerStream* QuicServerSession::CreateQuicCryptoServerStream(
+    const QuicCryptoServerConfig& crypto_config) {
+  return new QuicCryptoServerStream(crypto_config, this);
+}
+
+void QuicServerSession::OnConfigNegotiated() {
+  QuicSession::OnConfigNegotiated();
+  if (!FLAGS_enable_quic_fec ||
+      !config()->HasReceivedConnectionOptions() ||
+      !ContainsQuicTag(config()->ReceivedConnectionOptions(), kFHDR)) {
+    return;
+  }
+  // kFHDR config maps to FEC protection always for headers stream.
+  // TODO(jri): Add crypto stream in addition to headers for kHDR.
+  headers_stream_->set_fec_policy(FEC_PROTECT_ALWAYS);
+}
+
+void QuicServerSession::OnConnectionClosed(QuicErrorCode error,
+                                           bool from_peer) {
+  QuicSession::OnConnectionClosed(error, from_peer);
+  // In the unlikely event we get a connection close while doing an asynchronous
+  // crypto event, make sure we cancel the callback.
+  if (crypto_stream_.get() != nullptr) {
+    crypto_stream_->CancelOutstandingCallbacks();
+  }
+  visitor_->OnConnectionClosed(connection()->connection_id(), error);
+}
+
+void QuicServerSession::OnWriteBlocked() {
+  QuicSession::OnWriteBlocked();
+  visitor_->OnWriteBlocked(connection());
+}
+
+void QuicServerSession::OnCongestionWindowChange(QuicTime now) {
+  if (connection()->version() <= QUIC_VERSION_21) {
+    return;
+  }
+
+  // If not enough time has passed since the last time we sent an update to the
+  // client, then return early.
+  const QuicSentPacketManager& sent_packet_manager =
+      connection()->sent_packet_manager();
+  int64 srtt_ms =
+      sent_packet_manager.GetRttStats()->SmoothedRtt().ToMilliseconds();
+  int64 now_ms = now.Subtract(last_server_config_update_time_).ToMilliseconds();
+  if (now_ms < (kMinIntervalBetweenServerConfigUpdatesRTTs * srtt_ms) ||
+      now_ms < kMinIntervalBetweenServerConfigUpdatesMs) {
+    return;
+  }
+
+  // If the bandwidth recorder does not have a valid estimate, return early.
+  const QuicSustainedBandwidthRecorder& bandwidth_recorder =
+      sent_packet_manager.SustainedBandwidthRecorder();
+  if (!bandwidth_recorder.HasEstimate()) {
+    return;
+  }
+
+  // The bandwidth recorder has recorded at least one sustained bandwidth
+  // estimate. Check that it's substantially different from the last one that
+  // we sent to the client, and if so, send the new one.
+  QuicBandwidth new_bandwidth_estimate = bandwidth_recorder.BandwidthEstimate();
+
+  int64 bandwidth_delta =
+      std::abs(new_bandwidth_estimate.ToBitsPerSecond() -
+               bandwidth_estimate_sent_to_client_.ToBitsPerSecond());
+
+  // Define "substantial" difference as a 50% increase or decrease from the
+  // last estimate.
+  bool substantial_difference =
+      bandwidth_delta >
+      0.5 * bandwidth_estimate_sent_to_client_.ToBitsPerSecond();
+  if (!substantial_difference) {
+    return;
+  }
+
+  bandwidth_estimate_sent_to_client_ = new_bandwidth_estimate;
+  DVLOG(1) << "Server: sending new bandwidth estimate (KBytes/s): "
+           << bandwidth_estimate_sent_to_client_.ToKBytesPerSecond();
+
+  // Include max bandwidth in the update.
+  QuicBandwidth max_bandwidth_estimate =
+      bandwidth_recorder.MaxBandwidthEstimate();
+  int32 max_bandwidth_timestamp = bandwidth_recorder.MaxBandwidthTimestamp();
+
+  // Fill the proto before passing it to the crypto stream to send.
+  CachedNetworkParameters cached_network_params;
+  cached_network_params.set_bandwidth_estimate_bytes_per_second(
+      bandwidth_estimate_sent_to_client_.ToBytesPerSecond());
+  cached_network_params.set_max_bandwidth_estimate_bytes_per_second(
+      max_bandwidth_estimate.ToBytesPerSecond());
+  cached_network_params.set_max_bandwidth_timestamp_seconds(
+      max_bandwidth_timestamp);
+  cached_network_params.set_min_rtt_ms(
+      sent_packet_manager.GetRttStats()->min_rtt().ToMilliseconds());
+  cached_network_params.set_previous_connection_state(
+      bandwidth_recorder.EstimateRecordedDuringSlowStart()
+          ? CachedNetworkParameters::SLOW_START
+          : CachedNetworkParameters::CONGESTION_AVOIDANCE);
+  cached_network_params.set_timestamp(
+      connection()->clock()->WallNow().ToUNIXSeconds());
+  if (!serving_region_.empty()) {
+    cached_network_params.set_serving_region(serving_region_);
+  }
+
+  crypto_stream_->SendServerConfigUpdate(&cached_network_params);
+  last_server_config_update_time_ = now;
+}
+
+bool QuicServerSession::ShouldCreateIncomingDataStream(QuicStreamId id) {
+  if (id % 2 == 0) {
+    DVLOG(1) << "Invalid incoming even stream_id:" << id;
+    connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID);
+    return false;
+  }
+  if (GetNumOpenStreams() >= get_max_open_streams()) {
+    DVLOG(1) << "Failed to create a new incoming stream with id:" << id
+             << " Already " << GetNumOpenStreams() << " streams open (max "
+             << get_max_open_streams() << ").";
+    connection()->SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS);
+    return false;
+  }
+  return true;
+}
+
+QuicDataStream* QuicServerSession::CreateIncomingDataStream(
+    QuicStreamId id) {
+  if (!ShouldCreateIncomingDataStream(id)) {
+    return nullptr;
+  }
+
+  return new QuicSpdyServerStream(id, this);
+}
+
+QuicDataStream* QuicServerSession::CreateOutgoingDataStream() {
+  DLOG(ERROR) << "Server push not yet supported";
+  return nullptr;
+}
+
+QuicCryptoServerStream* QuicServerSession::GetCryptoStream() {
+  return crypto_stream_.get();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_server_session.h b/net/quic/quic_server_session.h
new file mode 100644
index 0000000..a2e4feb
--- /dev/null
+++ b/net/quic/quic_server_session.h
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A server specific QuicSession subclass.
+
+#ifndef NET_QUIC_QUIC_SERVER_SESSION_H_
+#define NET_QUIC_QUIC_SERVER_SESSION_H_
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_crypto_server_stream.h"
+#include "net/quic/quic_per_connection_packet_writer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+class QuicBlockedWriterInterface;
+class QuicConfig;
+class QuicConnection;
+class QuicCryptoServerConfig;
+class ReliableQuicStream;
+
+namespace test {
+class QuicServerSessionPeer;
+}  // namespace test
+
+// An interface from the session to the entity owning the session.
+// This lets the session notify its owner (the Dispatcher) when the connection
+// is closed or blocked.
+class QuicServerSessionVisitor {
+ public:
+  virtual ~QuicServerSessionVisitor() {}
+
+  virtual void OnConnectionClosed(QuicConnectionId connection_id,
+                                  QuicErrorCode error) = 0;
+  virtual void OnWriteBlocked(QuicBlockedWriterInterface* blocked_writer) = 0;
+};
+
+class QuicServerSession : public QuicSession {
+ public:
+  QuicServerSession(const QuicConfig& config,
+                    QuicConnection* connection,
+                    QuicServerSessionVisitor* visitor);
+
+  // Override the base class to notify the owner of the connection close.
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) OVERRIDE;
+  virtual void OnWriteBlocked() OVERRIDE;
+
+  // Sends a server config update to the client, containing new bandwidth
+  // estimate.
+  virtual void OnCongestionWindowChange(QuicTime now) OVERRIDE;
+
+  virtual ~QuicServerSession();
+
+  virtual void InitializeSession(const QuicCryptoServerConfig& crypto_config);
+
+  const QuicCryptoServerStream* crypto_stream() const {
+    return crypto_stream_.get();
+  }
+
+  // Override base class to process FEC config received from client.
+  virtual void OnConfigNegotiated() OVERRIDE;
+
+  void set_serving_region(string serving_region) {
+    serving_region_ = serving_region;
+  }
+
+ protected:
+  // QuicSession methods:
+  virtual QuicDataStream* CreateIncomingDataStream(QuicStreamId id) OVERRIDE;
+  virtual QuicDataStream* CreateOutgoingDataStream() OVERRIDE;
+  virtual QuicCryptoServerStream* GetCryptoStream() OVERRIDE;
+
+  // If we should create an incoming stream, returns true. Otherwise
+  // does error handling, including communicating the error to the client and
+  // possibly closing the connection, and returns false.
+  virtual bool ShouldCreateIncomingDataStream(QuicStreamId id);
+
+  virtual QuicCryptoServerStream* CreateQuicCryptoServerStream(
+      const QuicCryptoServerConfig& crypto_config);
+
+ private:
+  friend class test::QuicServerSessionPeer;
+
+  scoped_ptr<QuicCryptoServerStream> crypto_stream_;
+  QuicServerSessionVisitor* visitor_;
+
+  // The most recent bandwidth estimate sent to the client.
+  QuicBandwidth bandwidth_estimate_sent_to_client_;
+
+  // Text describing server location. Sent to the client as part of the bandwith
+  // estimate in the source-address token. Optional, can be left empty.
+  string serving_region_;
+
+  // Time at which we send the last SCUP to the client.
+  QuicTime last_server_config_update_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicServerSession);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SERVER_SESSION_H_
diff --git a/net/quic/quic_server_test.cc b/net/quic/quic_server_test.cc
new file mode 100644
index 0000000..9e4e894
--- /dev/null
+++ b/net/quic/quic_server_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_server.h"
+
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/mock_quic_dispatcher.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace net {
+namespace test {
+
+namespace {
+
+// TODO(dmz) Remove "Chrome" part of name once net/tools/quic is deleted.
+class QuicChromeServerDispatchPacketTest : public ::testing::Test {
+ public:
+  QuicChromeServerDispatchPacketTest()
+      : crypto_config_("blah", QuicRandom::GetInstance()),
+        dispatcher_(config_,
+                    crypto_config_,
+                    new QuicDispatcher::DefaultPacketWriterFactory(),
+                    &helper_) {
+    dispatcher_.Initialize(nullptr);
+  }
+
+  void DispatchPacket(const QuicEncryptedPacket& packet) {
+    IPEndPoint client_addr, server_addr;
+    dispatcher_.ProcessPacket(server_addr, client_addr, packet);
+  }
+
+ protected:
+  QuicConfig config_;
+  QuicCryptoServerConfig crypto_config_;
+  MockHelper helper_;
+  MockQuicDispatcher dispatcher_;
+};
+
+TEST_F(QuicChromeServerDispatchPacketTest, DispatchPacket) {
+  unsigned char valid_packet[] = {
+    // public flags (8 byte connection_id)
+    0x3C,
+    // connection_id
+    0x10, 0x32, 0x54, 0x76,
+    0x98, 0xBA, 0xDC, 0xFE,
+    // packet sequence number
+    0xBC, 0x9A, 0x78, 0x56,
+    0x34, 0x12,
+    // private flags
+    0x00 };
+  QuicEncryptedPacket encrypted_valid_packet(QuicUtils::AsChars(valid_packet),
+                                             arraysize(valid_packet), false);
+
+  EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _)).Times(1);
+  DispatchPacket(encrypted_valid_packet);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_session.cc b/net/quic/quic_session.cc
new file mode 100644
index 0000000..cef5656
--- /dev/null
+++ b/net/quic/quic_session.cc
@@ -0,0 +1,785 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_session.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_flow_controller.h"
+#include "net/quic/quic_headers_stream.h"
+#include "net/ssl/ssl_info.h"
+
+using base::StringPiece;
+using base::hash_map;
+using base::hash_set;
+using std::make_pair;
+using std::max;
+using std::vector;
+
+namespace net {
+
+#define ENDPOINT (is_server() ? "Server: " : " Client: ")
+
+// We want to make sure we delete any closed streams in a safe manner.
+// To avoid deleting a stream in mid-operation, we have a simple shim between
+// us and the stream, so we can delete any streams when we return from
+// processing.
+//
+// We could just override the base methods, but this makes it easier to make
+// sure we don't miss any.
+class VisitorShim : public QuicConnectionVisitorInterface {
+ public:
+  explicit VisitorShim(QuicSession* session) : session_(session) {}
+
+  virtual void OnStreamFrames(const vector<QuicStreamFrame>& frames) OVERRIDE {
+    session_->OnStreamFrames(frames);
+    session_->PostProcessAfterData();
+  }
+  virtual void OnRstStream(const QuicRstStreamFrame& frame) OVERRIDE {
+    session_->OnRstStream(frame);
+    session_->PostProcessAfterData();
+  }
+
+  virtual void OnGoAway(const QuicGoAwayFrame& frame) OVERRIDE {
+    session_->OnGoAway(frame);
+    session_->PostProcessAfterData();
+  }
+
+  virtual void OnWindowUpdateFrames(const vector<QuicWindowUpdateFrame>& frames)
+      OVERRIDE {
+    session_->OnWindowUpdateFrames(frames);
+    session_->PostProcessAfterData();
+  }
+
+  virtual void OnBlockedFrames(const vector<QuicBlockedFrame>& frames)
+      OVERRIDE {
+    session_->OnBlockedFrames(frames);
+    session_->PostProcessAfterData();
+  }
+
+  virtual void OnCanWrite() OVERRIDE {
+    session_->OnCanWrite();
+    session_->PostProcessAfterData();
+  }
+
+  virtual void OnCongestionWindowChange(QuicTime now) OVERRIDE {
+    session_->OnCongestionWindowChange(now);
+  }
+
+  virtual void OnSuccessfulVersionNegotiation(
+      const QuicVersion& version) OVERRIDE {
+    session_->OnSuccessfulVersionNegotiation(version);
+  }
+
+  virtual void OnConnectionClosed(
+      QuicErrorCode error, bool from_peer) OVERRIDE {
+    session_->OnConnectionClosed(error, from_peer);
+    // The session will go away, so don't bother with cleanup.
+  }
+
+  virtual void OnWriteBlocked() OVERRIDE {
+    session_->OnWriteBlocked();
+  }
+
+  virtual bool WillingAndAbleToWrite() const OVERRIDE {
+    return session_->WillingAndAbleToWrite();
+  }
+
+  virtual bool HasPendingHandshake() const OVERRIDE {
+    return session_->HasPendingHandshake();
+  }
+
+  virtual bool HasOpenDataStreams() const OVERRIDE {
+    return session_->HasOpenDataStreams();
+  }
+
+ private:
+  QuicSession* session_;
+};
+
+QuicSession::QuicSession(QuicConnection* connection, const QuicConfig& config)
+    : connection_(connection),
+      visitor_shim_(new VisitorShim(this)),
+      config_(config),
+      max_open_streams_(config_.MaxStreamsPerConnection()),
+      next_stream_id_(is_server() ? 2 : 5),
+      largest_peer_created_stream_id_(0),
+      error_(QUIC_NO_ERROR),
+      goaway_received_(false),
+      goaway_sent_(false),
+      has_pending_handshake_(false) {
+  if (connection_->version() <= QUIC_VERSION_19) {
+    flow_controller_.reset(new QuicFlowController(
+        connection_.get(), 0, is_server(), kDefaultFlowControlSendWindow,
+        config_.GetInitialFlowControlWindowToSend(),
+        config_.GetInitialFlowControlWindowToSend()));
+  } else {
+    flow_controller_.reset(new QuicFlowController(
+        connection_.get(), 0, is_server(), kDefaultFlowControlSendWindow,
+        config_.GetInitialSessionFlowControlWindowToSend(),
+        config_.GetInitialSessionFlowControlWindowToSend()));
+  }
+}
+
+void QuicSession::InitializeSession() {
+  connection_->set_visitor(visitor_shim_.get());
+  connection_->SetFromConfig(config_);
+  if (!FLAGS_quic_unified_timeouts && connection_->connected()) {
+    connection_->SetOverallConnectionTimeout(
+        config_.max_time_before_crypto_handshake());
+  }
+  headers_stream_.reset(new QuicHeadersStream(this));
+}
+
+QuicSession::~QuicSession() {
+  STLDeleteElements(&closed_streams_);
+  STLDeleteValues(&stream_map_);
+
+  DLOG_IF(WARNING,
+          locally_closed_streams_highest_offset_.size() > max_open_streams_)
+      << "Surprisingly high number of locally closed streams still waiting for "
+         "final byte offset: " << locally_closed_streams_highest_offset_.size();
+}
+
+void QuicSession::OnStreamFrames(const vector<QuicStreamFrame>& frames) {
+  for (size_t i = 0; i < frames.size(); ++i) {
+    // TODO(rch) deal with the error case of stream id 0.
+    const QuicStreamFrame& frame = frames[i];
+    QuicStreamId stream_id = frame.stream_id;
+    ReliableQuicStream* stream = GetStream(stream_id);
+    if (!stream) {
+      // The stream no longer exists, but we may still be interested in the
+      // final stream byte offset sent by the peer. A frame with a FIN can give
+      // us this offset.
+      if (frame.fin) {
+        QuicStreamOffset final_byte_offset =
+            frame.offset + frame.data.TotalBufferSize();
+        UpdateFlowControlOnFinalReceivedByteOffset(stream_id,
+                                                   final_byte_offset);
+      }
+
+      continue;
+    }
+    stream->OnStreamFrame(frames[i]);
+  }
+}
+
+void QuicSession::OnStreamHeaders(QuicStreamId stream_id,
+                                  StringPiece headers_data) {
+  QuicDataStream* stream = GetDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnStreamHeaders(headers_data);
+}
+
+void QuicSession::OnStreamHeadersPriority(QuicStreamId stream_id,
+                                          QuicPriority priority) {
+  QuicDataStream* stream = GetDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnStreamHeadersPriority(priority);
+}
+
+void QuicSession::OnStreamHeadersComplete(QuicStreamId stream_id,
+                                          bool fin,
+                                          size_t frame_len) {
+  QuicDataStream* stream = GetDataStream(stream_id);
+  if (!stream) {
+    // It's quite possible to receive headers after a stream has been reset.
+    return;
+  }
+  stream->OnStreamHeadersComplete(fin, frame_len);
+}
+
+void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
+  if (frame.stream_id == kCryptoStreamId) {
+    connection()->SendConnectionCloseWithDetails(
+        QUIC_INVALID_STREAM_ID,
+        "Attempt to reset the crypto stream");
+    return;
+  }
+  if (frame.stream_id == kHeadersStreamId) {
+    connection()->SendConnectionCloseWithDetails(
+        QUIC_INVALID_STREAM_ID,
+        "Attempt to reset the headers stream");
+    return;
+  }
+
+  QuicDataStream* stream = GetDataStream(frame.stream_id);
+  if (!stream) {
+    // The RST frame contains the final byte offset for the stream: we can now
+    // update the connection level flow controller if needed.
+    UpdateFlowControlOnFinalReceivedByteOffset(frame.stream_id,
+                                               frame.byte_offset);
+    return;  // Errors are handled by GetStream.
+  }
+
+  stream->OnStreamReset(frame);
+}
+
+void QuicSession::OnGoAway(const QuicGoAwayFrame& frame) {
+  DCHECK(frame.last_good_stream_id < next_stream_id_);
+  goaway_received_ = true;
+}
+
+void QuicSession::OnConnectionClosed(QuicErrorCode error, bool from_peer) {
+  DCHECK(!connection_->connected());
+  if (error_ == QUIC_NO_ERROR) {
+    error_ = error;
+  }
+
+  while (!stream_map_.empty()) {
+    DataStreamMap::iterator it = stream_map_.begin();
+    QuicStreamId id = it->first;
+    it->second->OnConnectionClosed(error, from_peer);
+    // The stream should call CloseStream as part of OnConnectionClosed.
+    if (stream_map_.find(id) != stream_map_.end()) {
+      LOG(DFATAL) << ENDPOINT
+                  << "Stream failed to close under OnConnectionClosed";
+      CloseStream(id);
+    }
+  }
+}
+
+void QuicSession::OnWindowUpdateFrames(
+    const vector<QuicWindowUpdateFrame>& frames) {
+  bool connection_window_updated = false;
+  for (size_t i = 0; i < frames.size(); ++i) {
+    // Stream may be closed by the time we receive a WINDOW_UPDATE, so we can't
+    // assume that it still exists.
+    QuicStreamId stream_id = frames[i].stream_id;
+    if (stream_id == kConnectionLevelId) {
+      // This is a window update that applies to the connection, rather than an
+      // individual stream.
+      DVLOG(1) << ENDPOINT
+               << "Received connection level flow control window update with "
+                  "byte offset: " << frames[i].byte_offset;
+      if (flow_controller_->UpdateSendWindowOffset(frames[i].byte_offset)) {
+        connection_window_updated = true;
+      }
+      continue;
+    }
+
+    if (connection_->version() < QUIC_VERSION_21 &&
+        (stream_id == kCryptoStreamId || stream_id == kHeadersStreamId)) {
+      DLOG(DFATAL) << "WindowUpdate for stream " << stream_id << " in version "
+                   << QuicVersionToString(connection_->version());
+      return;
+    }
+
+    ReliableQuicStream* stream = GetStream(stream_id);
+    if (stream) {
+      stream->OnWindowUpdateFrame(frames[i]);
+    }
+  }
+
+  // Connection level flow control window has increased, so blocked streams can
+  // write again.
+  if (connection_window_updated) {
+    OnCanWrite();
+  }
+}
+
+void QuicSession::OnBlockedFrames(const vector<QuicBlockedFrame>& frames) {
+  for (size_t i = 0; i < frames.size(); ++i) {
+    // TODO(rjshade): Compare our flow control receive windows for specified
+    //                streams: if we have a large window then maybe something
+    //                had gone wrong with the flow control accounting.
+    DVLOG(1) << ENDPOINT << "Received BLOCKED frame with stream id: "
+             << frames[i].stream_id;
+  }
+}
+
+void QuicSession::OnCanWrite() {
+  // We limit the number of writes to the number of pending streams. If more
+  // streams become pending, WillingAndAbleToWrite will be true, which will
+  // cause the connection to request resumption before yielding to other
+  // connections.
+  size_t num_writes = write_blocked_streams_.NumBlockedStreams();
+  if (flow_controller_->IsBlocked()) {
+    // If we are connection level flow control blocked, then only allow the
+    // crypto and headers streams to try writing as all other streams will be
+    // blocked.
+    num_writes = 0;
+    if (write_blocked_streams_.crypto_stream_blocked()) {
+      num_writes += 1;
+    }
+    if (write_blocked_streams_.headers_stream_blocked()) {
+      num_writes += 1;
+    }
+  }
+  if (num_writes == 0) {
+    return;
+  }
+
+  QuicConnection::ScopedPacketBundler ack_bundler(
+      connection_.get(), QuicConnection::NO_ACK);
+  for (size_t i = 0; i < num_writes; ++i) {
+    if (!(write_blocked_streams_.HasWriteBlockedCryptoOrHeadersStream() ||
+          write_blocked_streams_.HasWriteBlockedDataStreams())) {
+      // Writing one stream removed another!? Something's broken.
+      LOG(DFATAL) << "WriteBlockedStream is missing";
+      connection_->CloseConnection(QUIC_INTERNAL_ERROR, false);
+      return;
+    }
+    if (!connection_->CanWriteStreamData()) {
+      return;
+    }
+    QuicStreamId stream_id = write_blocked_streams_.PopFront();
+    if (stream_id == kCryptoStreamId) {
+      has_pending_handshake_ = false;  // We just popped it.
+    }
+    ReliableQuicStream* stream = GetStream(stream_id);
+    if (stream != nullptr && !stream->flow_controller()->IsBlocked()) {
+      // If the stream can't write all bytes, it'll re-add itself to the blocked
+      // list.
+      stream->OnCanWrite();
+    }
+  }
+}
+
+bool QuicSession::WillingAndAbleToWrite() const {
+  // If the crypto or headers streams are blocked, we want to schedule a write -
+  // they don't get blocked by connection level flow control. Otherwise only
+  // schedule a write if we are not flow control blocked at the connection
+  // level.
+  return write_blocked_streams_.HasWriteBlockedCryptoOrHeadersStream() ||
+         (!flow_controller_->IsBlocked() &&
+          write_blocked_streams_.HasWriteBlockedDataStreams());
+}
+
+bool QuicSession::HasPendingHandshake() const {
+  return has_pending_handshake_;
+}
+
+bool QuicSession::HasOpenDataStreams() const {
+  return GetNumOpenStreams() > 0;
+}
+
+QuicConsumedData QuicSession::WritevData(
+    QuicStreamId id,
+    const IOVector& data,
+    QuicStreamOffset offset,
+    bool fin,
+    FecProtection fec_protection,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  return connection_->SendStreamData(id, data, offset, fin, fec_protection,
+                                     ack_notifier_delegate);
+}
+
+size_t QuicSession::WriteHeaders(
+    QuicStreamId id,
+    const SpdyHeaderBlock& headers,
+    bool fin,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  return headers_stream_->WriteHeaders(id, headers, fin, ack_notifier_delegate);
+}
+
+void QuicSession::SendRstStream(QuicStreamId id,
+                                QuicRstStreamErrorCode error,
+                                QuicStreamOffset bytes_written) {
+  if (connection()->connected()) {
+    // Only send a RST_STREAM frame if still connected.
+    connection_->SendRstStream(id, error, bytes_written);
+  }
+  CloseStreamInner(id, true);
+}
+
+void QuicSession::SendGoAway(QuicErrorCode error_code, const string& reason) {
+  if (goaway_sent_) {
+    return;
+  }
+  goaway_sent_ = true;
+  connection_->SendGoAway(error_code, largest_peer_created_stream_id_, reason);
+}
+
+void QuicSession::CloseStream(QuicStreamId stream_id) {
+  CloseStreamInner(stream_id, false);
+}
+
+void QuicSession::CloseStreamInner(QuicStreamId stream_id,
+                                   bool locally_reset) {
+  DVLOG(1) << ENDPOINT << "Closing stream " << stream_id;
+
+  DataStreamMap::iterator it = stream_map_.find(stream_id);
+  if (it == stream_map_.end()) {
+    DVLOG(1) << ENDPOINT << "Stream is already closed: " << stream_id;
+    return;
+  }
+  QuicDataStream* stream = it->second;
+
+  // Tell the stream that a RST has been sent.
+  if (locally_reset) {
+    stream->set_rst_sent(true);
+  }
+
+  closed_streams_.push_back(it->second);
+
+  // If we haven't received a FIN or RST for this stream, we need to keep track
+  // of the how many bytes the stream's flow controller believes it has
+  // received, for accurate connection level flow control accounting.
+  if (!stream->HasFinalReceivedByteOffset() &&
+      stream->flow_controller()->IsEnabled()) {
+    locally_closed_streams_highest_offset_[stream_id] =
+        stream->flow_controller()->highest_received_byte_offset();
+  }
+
+  stream_map_.erase(it);
+  stream->OnClose();
+}
+
+void QuicSession::UpdateFlowControlOnFinalReceivedByteOffset(
+    QuicStreamId stream_id, QuicStreamOffset final_byte_offset) {
+  map<QuicStreamId, QuicStreamOffset>::iterator it =
+      locally_closed_streams_highest_offset_.find(stream_id);
+  if (it == locally_closed_streams_highest_offset_.end()) {
+    return;
+  }
+
+  DVLOG(1) << ENDPOINT << "Received final byte offset " << final_byte_offset
+           << " for stream " << stream_id;
+  uint64 offset_diff = final_byte_offset - it->second;
+  if (flow_controller_->UpdateHighestReceivedOffset(
+      flow_controller_->highest_received_byte_offset() + offset_diff)) {
+    // If the final offset violates flow control, close the connection now.
+    if (flow_controller_->FlowControlViolation()) {
+      connection_->SendConnectionClose(
+          QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA);
+      return;
+    }
+  }
+
+  flow_controller_->AddBytesConsumed(offset_diff);
+  locally_closed_streams_highest_offset_.erase(it);
+}
+
+bool QuicSession::IsEncryptionEstablished() {
+  return GetCryptoStream()->encryption_established();
+}
+
+bool QuicSession::IsCryptoHandshakeConfirmed() {
+  return GetCryptoStream()->handshake_confirmed();
+}
+
+void QuicSession::OnConfigNegotiated() {
+  connection_->SetFromConfig(config_);
+  QuicVersion version = connection()->version();
+
+  if (FLAGS_quic_allow_more_open_streams) {
+    uint32 max_streams = config_.MaxStreamsPerConnection();
+    if (is_server()) {
+      // A server should accept a small number of additional streams beyond the
+      // limit sent to the client. This helps avoid early connection termination
+      // when FIN/RSTs for old streams are lost or arrive out of order.
+      // Use a minimum number of additional streams, or a percentage increase,
+      // whichever is larger.
+      max_streams =
+          max(max_streams + kMaxStreamsMinimumIncrement,
+              static_cast<uint32>(max_streams * kMaxStreamsMultiplier));
+    }
+    set_max_open_streams(max_streams);
+  }
+
+  if (version <= QUIC_VERSION_19) {
+    // QUIC_VERSION_17,18,19 don't support independent stream/session flow
+    // control windows.
+    if (config_.HasReceivedInitialFlowControlWindowBytes()) {
+      // Streams which were created before the SHLO was received (0-RTT
+      // requests) are now informed of the peer's initial flow control window.
+      uint32 new_window = config_.ReceivedInitialFlowControlWindowBytes();
+      OnNewStreamFlowControlWindow(new_window);
+      OnNewSessionFlowControlWindow(new_window);
+    }
+
+    return;
+  }
+
+  // QUIC_VERSION_21 and higher can have independent stream and session flow
+  // control windows.
+  if (config_.HasReceivedInitialStreamFlowControlWindowBytes()) {
+    // Streams which were created before the SHLO was received (0-RTT
+    // requests) are now informed of the peer's initial flow control window.
+    OnNewStreamFlowControlWindow(
+        config_.ReceivedInitialStreamFlowControlWindowBytes());
+  }
+  if (config_.HasReceivedInitialSessionFlowControlWindowBytes()) {
+    OnNewSessionFlowControlWindow(
+        config_.ReceivedInitialSessionFlowControlWindowBytes());
+  }
+}
+
+void QuicSession::OnNewStreamFlowControlWindow(uint32 new_window) {
+  if (new_window < kDefaultFlowControlSendWindow) {
+    LOG(ERROR)
+        << "Peer sent us an invalid stream flow control send window: "
+        << new_window << ", below default: " << kDefaultFlowControlSendWindow;
+    if (connection_->connected()) {
+      connection_->SendConnectionClose(QUIC_FLOW_CONTROL_INVALID_WINDOW);
+    }
+    return;
+  }
+
+  // Inform all existing streams about the new window.
+  if (connection_->version() >= QUIC_VERSION_21) {
+    GetCryptoStream()->flow_controller()->UpdateSendWindowOffset(new_window);
+    headers_stream_->flow_controller()->UpdateSendWindowOffset(new_window);
+  }
+  for (DataStreamMap::iterator it = stream_map_.begin();
+       it != stream_map_.end(); ++it) {
+    it->second->flow_controller()->UpdateSendWindowOffset(new_window);
+  }
+}
+
+void QuicSession::OnNewSessionFlowControlWindow(uint32 new_window) {
+  if (new_window < kDefaultFlowControlSendWindow) {
+    LOG(ERROR)
+        << "Peer sent us an invalid session flow control send window: "
+        << new_window << ", below default: " << kDefaultFlowControlSendWindow;
+    if (connection_->connected()) {
+      connection_->SendConnectionClose(QUIC_FLOW_CONTROL_INVALID_WINDOW);
+    }
+    return;
+  }
+
+  flow_controller_->UpdateSendWindowOffset(new_window);
+}
+
+void QuicSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+  switch (event) {
+    // TODO(satyamshekhar): Move the logic of setting the encrypter/decrypter
+    // to QuicSession since it is the glue.
+    case ENCRYPTION_FIRST_ESTABLISHED:
+      break;
+
+    case ENCRYPTION_REESTABLISHED:
+      // Retransmit originally packets that were sent, since they can't be
+      // decrypted by the peer.
+      connection_->RetransmitUnackedPackets(ALL_INITIAL_RETRANSMISSION);
+      break;
+
+    case HANDSHAKE_CONFIRMED:
+      LOG_IF(DFATAL, !config_.negotiated()) << ENDPOINT
+          << "Handshake confirmed without parameter negotiation.";
+      // Discard originally encrypted packets, since they can't be decrypted by
+      // the peer.
+      connection_->NeuterUnencryptedPackets();
+      if (!FLAGS_quic_unified_timeouts) {
+        connection_->SetOverallConnectionTimeout(QuicTime::Delta::Infinite());
+      }
+      if (!FLAGS_quic_allow_more_open_streams) {
+        max_open_streams_ = config_.MaxStreamsPerConnection();
+      }
+      break;
+
+    default:
+      LOG(ERROR) << ENDPOINT << "Got unknown handshake event: " << event;
+  }
+}
+
+void QuicSession::OnCryptoHandshakeMessageSent(
+    const CryptoHandshakeMessage& message) {
+}
+
+void QuicSession::OnCryptoHandshakeMessageReceived(
+    const CryptoHandshakeMessage& message) {
+}
+
+QuicConfig* QuicSession::config() {
+  return &config_;
+}
+
+void QuicSession::ActivateStream(QuicDataStream* stream) {
+  DVLOG(1) << ENDPOINT << "num_streams: " << stream_map_.size()
+           << ". activating " << stream->id();
+  DCHECK_EQ(stream_map_.count(stream->id()), 0u);
+  stream_map_[stream->id()] = stream;
+}
+
+QuicStreamId QuicSession::GetNextStreamId() {
+  QuicStreamId id = next_stream_id_;
+  next_stream_id_ += 2;
+  return id;
+}
+
+ReliableQuicStream* QuicSession::GetStream(const QuicStreamId stream_id) {
+  if (stream_id == kCryptoStreamId) {
+    return GetCryptoStream();
+  }
+  if (stream_id == kHeadersStreamId) {
+    return headers_stream_.get();
+  }
+  return GetDataStream(stream_id);
+}
+
+QuicDataStream* QuicSession::GetDataStream(const QuicStreamId stream_id) {
+  if (stream_id == kCryptoStreamId) {
+    DLOG(FATAL) << "Attempt to call GetDataStream with the crypto stream id";
+    return nullptr;
+  }
+  if (stream_id == kHeadersStreamId) {
+    DLOG(FATAL) << "Attempt to call GetDataStream with the headers stream id";
+    return nullptr;
+  }
+
+  DataStreamMap::iterator it = stream_map_.find(stream_id);
+  if (it != stream_map_.end()) {
+    return it->second;
+  }
+
+  if (IsClosedStream(stream_id)) {
+    return nullptr;
+  }
+
+  if (stream_id % 2 == next_stream_id_ % 2) {
+    // We've received a frame for a locally-created stream that is not
+    // currently active.  This is an error.
+    if (connection()->connected()) {
+      connection()->SendConnectionClose(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+    }
+    return nullptr;
+  }
+
+  return GetIncomingDataStream(stream_id);
+}
+
+QuicDataStream* QuicSession::GetIncomingDataStream(QuicStreamId stream_id) {
+  if (IsClosedStream(stream_id)) {
+    return nullptr;
+  }
+
+  implicitly_created_streams_.erase(stream_id);
+  if (stream_id > largest_peer_created_stream_id_) {
+    if (stream_id - largest_peer_created_stream_id_ > kMaxStreamIdDelta) {
+      // We may already have sent a connection close due to multiple reset
+      // streams in the same packet.
+      if (connection()->connected()) {
+        LOG(ERROR) << "Trying to get stream: " << stream_id
+                   << ", largest peer created stream: "
+                   << largest_peer_created_stream_id_
+                   << ", max delta: " << kMaxStreamIdDelta;
+        connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID);
+      }
+      return nullptr;
+    }
+    if (largest_peer_created_stream_id_ == 0) {
+      if (is_server()) {
+        largest_peer_created_stream_id_= 3;
+      } else {
+        largest_peer_created_stream_id_= 1;
+      }
+    }
+    for (QuicStreamId id = largest_peer_created_stream_id_ + 2;
+         id < stream_id;
+         id += 2) {
+      implicitly_created_streams_.insert(id);
+    }
+    largest_peer_created_stream_id_ = stream_id;
+  }
+  QuicDataStream* stream = CreateIncomingDataStream(stream_id);
+  if (stream == nullptr) {
+    return nullptr;
+  }
+  ActivateStream(stream);
+  return stream;
+}
+
+void QuicSession::set_max_open_streams(size_t max_open_streams) {
+  DVLOG(1) << "Setting max_open_streams_ to " << max_open_streams;
+  max_open_streams_ = max_open_streams;
+}
+
+bool QuicSession::IsClosedStream(QuicStreamId id) {
+  DCHECK_NE(0u, id);
+  if (id == kCryptoStreamId) {
+    return false;
+  }
+  if (id == kHeadersStreamId) {
+    return false;
+  }
+  if (ContainsKey(stream_map_, id)) {
+    // Stream is active
+    return false;
+  }
+  if (id % 2 == next_stream_id_ % 2) {
+    // Locally created streams are strictly in-order.  If the id is in the
+    // range of created streams and it's not active, it must have been closed.
+    return id < next_stream_id_;
+  }
+  // For peer created streams, we also need to consider implicitly created
+  // streams.
+  return id <= largest_peer_created_stream_id_ &&
+      implicitly_created_streams_.count(id) == 0;
+}
+
+size_t QuicSession::GetNumOpenStreams() const {
+  return stream_map_.size() + implicitly_created_streams_.size();
+}
+
+void QuicSession::MarkWriteBlocked(QuicStreamId id, QuicPriority priority) {
+#ifndef NDEBUG
+  ReliableQuicStream* stream = GetStream(id);
+  if (stream != nullptr) {
+    LOG_IF(DFATAL, priority != stream->EffectivePriority())
+        << ENDPOINT << "Stream " << id
+        << "Priorities do not match.  Got: " << priority
+        << " Expected: " << stream->EffectivePriority();
+  } else {
+    LOG(DFATAL) << "Marking unknown stream " << id << " blocked.";
+  }
+#endif
+
+  if (id == kCryptoStreamId) {
+    DCHECK(!has_pending_handshake_);
+    has_pending_handshake_ = true;
+    // TODO(jar): Be sure to use the highest priority for the crypto stream,
+    // perhaps by adding a "special" priority for it that is higher than
+    // kHighestPriority.
+    priority = kHighestPriority;
+  }
+  write_blocked_streams_.PushBack(id, priority);
+}
+
+bool QuicSession::HasDataToWrite() const {
+  return write_blocked_streams_.HasWriteBlockedCryptoOrHeadersStream() ||
+         write_blocked_streams_.HasWriteBlockedDataStreams() ||
+         connection_->HasQueuedData();
+}
+
+bool QuicSession::GetSSLInfo(SSLInfo* ssl_info) const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+void QuicSession::PostProcessAfterData() {
+  STLDeleteElements(&closed_streams_);
+  closed_streams_.clear();
+
+  if (FLAGS_close_quic_connection_unfinished_streams_2 &&
+      connection()->connected() &&
+      locally_closed_streams_highest_offset_.size() > max_open_streams_) {
+    // A buggy client may fail to send FIN/RSTs. Don't tolerate this.
+    connection_->SendConnectionClose(QUIC_TOO_MANY_UNFINISHED_STREAMS);
+  }
+}
+
+void QuicSession::OnSuccessfulVersionNegotiation(const QuicVersion& version) {
+  if (version < QUIC_VERSION_19) {
+    flow_controller_->Disable();
+  }
+
+  // Disable stream level flow control based on negotiated version. Streams may
+  // have been created with a different version.
+  if (version < QUIC_VERSION_21) {
+    GetCryptoStream()->flow_controller()->Disable();
+    headers_stream_->flow_controller()->Disable();
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_session.h b/net/quic/quic_session.h
new file mode 100644
index 0000000..7a52fba
--- /dev/null
+++ b/net/quic/quic_session.h
@@ -0,0 +1,331 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A QuicSession, which demuxes a single connection to individual streams.
+
+#ifndef NET_QUIC_QUIC_SESSION_H_
+#define NET_QUIC_QUIC_SESSION_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_data_stream.h"
+#include "net/quic/quic_headers_stream.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_write_blocked_list.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+class QuicCryptoStream;
+class QuicFlowController;
+class ReliableQuicStream;
+class SSLInfo;
+class VisitorShim;
+
+namespace test {
+class QuicSessionPeer;
+}  // namespace test
+
+class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
+ public:
+  // CryptoHandshakeEvent enumerates the events generated by a QuicCryptoStream.
+  enum CryptoHandshakeEvent {
+    // ENCRYPTION_FIRST_ESTABLISHED indicates that a full client hello has been
+    // sent by a client and that subsequent packets will be encrypted. (Client
+    // only.)
+    ENCRYPTION_FIRST_ESTABLISHED,
+    // ENCRYPTION_REESTABLISHED indicates that a client hello was rejected by
+    // the server and thus the encryption key has been updated. Therefore the
+    // connection should resend any packets that were sent under
+    // ENCRYPTION_INITIAL. (Client only.)
+    ENCRYPTION_REESTABLISHED,
+    // HANDSHAKE_CONFIRMED, in a client, indicates the the server has accepted
+    // our handshake. In a server it indicates that a full, valid client hello
+    // has been received. (Client and server.)
+    HANDSHAKE_CONFIRMED,
+  };
+
+  QuicSession(QuicConnection* connection, const QuicConfig& config);
+  void InitializeSession();
+
+  virtual ~QuicSession();
+
+  // QuicConnectionVisitorInterface methods:
+  virtual void OnStreamFrames(
+      const std::vector<QuicStreamFrame>& frames) OVERRIDE;
+  virtual void OnRstStream(const QuicRstStreamFrame& frame) OVERRIDE;
+  virtual void OnGoAway(const QuicGoAwayFrame& frame) OVERRIDE;
+  virtual void OnWindowUpdateFrames(
+      const std::vector<QuicWindowUpdateFrame>& frames) OVERRIDE;
+  virtual void OnBlockedFrames(
+      const std::vector<QuicBlockedFrame>& frames) OVERRIDE;
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer) OVERRIDE;
+  virtual void OnWriteBlocked() OVERRIDE {}
+  virtual void OnSuccessfulVersionNegotiation(
+      const QuicVersion& version) OVERRIDE;
+  virtual void OnCanWrite() OVERRIDE;
+  virtual void OnCongestionWindowChange(QuicTime now) OVERRIDE {}
+  virtual bool WillingAndAbleToWrite() const OVERRIDE;
+  virtual bool HasPendingHandshake() const OVERRIDE;
+  virtual bool HasOpenDataStreams() const OVERRIDE;
+
+  // Called by the headers stream when headers have been received for a stream.
+  virtual void OnStreamHeaders(QuicStreamId stream_id,
+                               base::StringPiece headers_data);
+  // Called by the headers stream when headers with a priority have been
+  // received for this stream.  This method will only be called for server
+  // streams.
+  virtual void OnStreamHeadersPriority(QuicStreamId stream_id,
+                                       QuicPriority priority);
+  // Called by the headers stream when headers have been completely received
+  // for a stream.  |fin| will be true if the fin flag was set in the headers
+  // frame.
+  virtual void OnStreamHeadersComplete(QuicStreamId stream_id,
+                                       bool fin,
+                                       size_t frame_len);
+
+  // Called by streams when they want to write data to the peer.
+  // Returns a pair with the number of bytes consumed from data, and a boolean
+  // indicating if the fin bit was consumed.  This does not indicate the data
+  // has been sent on the wire: it may have been turned into a packet and queued
+  // if the socket was unexpectedly blocked.  |fec_protection| indicates if
+  // data is to be FEC protected. Note that data that is sent immediately
+  // following MUST_FEC_PROTECT data may get protected by falling within the
+  // same FEC group.
+  // If provided, |ack_notifier_delegate| will be registered to be notified when
+  // we have seen ACKs for all packets resulting from this call.
+  virtual QuicConsumedData WritevData(
+      QuicStreamId id,
+      const IOVector& data,
+      QuicStreamOffset offset,
+      bool fin,
+      FecProtection fec_protection,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // Writes |headers| for the stream |id| to the dedicated headers stream.
+  // If |fin| is true, then no more data will be sent for the stream |id|.
+  // If provided, |ack_notifier_delegate| will be registered to be notified when
+  // we have seen ACKs for all packets resulting from this call.
+  size_t WriteHeaders(
+      QuicStreamId id,
+      const SpdyHeaderBlock& headers,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // Called by streams when they want to close the stream in both directions.
+  virtual void SendRstStream(QuicStreamId id,
+                             QuicRstStreamErrorCode error,
+                             QuicStreamOffset bytes_written);
+
+  // Called when the session wants to go away and not accept any new streams.
+  void SendGoAway(QuicErrorCode error_code, const std::string& reason);
+
+  // Removes the stream associated with 'stream_id' from the active stream map.
+  virtual void CloseStream(QuicStreamId stream_id);
+
+  // Returns true if outgoing packets will be encrypted, even if the server
+  // hasn't confirmed the handshake yet.
+  virtual bool IsEncryptionEstablished();
+
+  // For a client, returns true if the server has confirmed our handshake. For
+  // a server, returns true if a full, valid client hello has been received.
+  virtual bool IsCryptoHandshakeConfirmed();
+
+  // Called by the QuicCryptoStream when a new QuicConfig has been negotiated.
+  virtual void OnConfigNegotiated();
+
+  // Called by the QuicCryptoStream when the handshake enters a new state.
+  //
+  // Clients will call this function in the order:
+  //   ENCRYPTION_FIRST_ESTABLISHED
+  //   zero or more ENCRYPTION_REESTABLISHED
+  //   HANDSHAKE_CONFIRMED
+  //
+  // Servers will simply call it once with HANDSHAKE_CONFIRMED.
+  virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event);
+
+  // Called by the QuicCryptoStream when a handshake message is sent.
+  virtual void OnCryptoHandshakeMessageSent(
+      const CryptoHandshakeMessage& message);
+
+  // Called by the QuicCryptoStream when a handshake message is received.
+  virtual void OnCryptoHandshakeMessageReceived(
+      const CryptoHandshakeMessage& message);
+
+  // Returns mutable config for this session. Returned config is owned
+  // by QuicSession.
+  QuicConfig* config();
+
+  // Returns true if the stream existed previously and has been closed.
+  // Returns false if the stream is still active or if the stream has
+  // not yet been created.
+  bool IsClosedStream(QuicStreamId id);
+
+  QuicConnection* connection() { return connection_.get(); }
+  const QuicConnection* connection() const { return connection_.get(); }
+  size_t num_active_requests() const { return stream_map_.size(); }
+  const IPEndPoint& peer_address() const {
+    return connection_->peer_address();
+  }
+  QuicConnectionId connection_id() const {
+    return connection_->connection_id();
+  }
+
+  // Returns the number of currently open streams, including those which have
+  // been implicitly created, but excluding the reserved headers and crypto
+  // streams.
+  virtual size_t GetNumOpenStreams() const;
+
+  void MarkWriteBlocked(QuicStreamId id, QuicPriority priority);
+
+  // Returns true if the session has data to be sent, either queued in the
+  // connection, or in a write-blocked stream.
+  bool HasDataToWrite() const;
+
+  bool goaway_received() const {
+    return goaway_received_;
+  }
+
+  bool goaway_sent() const {
+    return goaway_sent_;
+  }
+
+  // Gets the SSL connection information.
+  virtual bool GetSSLInfo(SSLInfo* ssl_info) const;
+
+  QuicErrorCode error() const { return error_; }
+
+  bool is_server() const { return connection_->is_server(); }
+
+  QuicFlowController* flow_controller() { return flow_controller_.get(); }
+
+  size_t get_max_open_streams() const { return max_open_streams_; }
+
+ protected:
+  typedef base::hash_map<QuicStreamId, QuicDataStream*> DataStreamMap;
+
+  // Creates a new stream, owned by the caller, to handle a peer-initiated
+  // stream.  Returns nullptr and does error handling if the stream can not be
+  // created.
+  virtual QuicDataStream* CreateIncomingDataStream(QuicStreamId id) = 0;
+
+  // Create a new stream, owned by the caller, to handle a locally-initiated
+  // stream.  Returns nullptr if max streams have already been opened.
+  virtual QuicDataStream* CreateOutgoingDataStream() = 0;
+
+  // Return the reserved crypto stream.
+  virtual QuicCryptoStream* GetCryptoStream() = 0;
+
+  // Adds 'stream' to the active stream map.
+  virtual void ActivateStream(QuicDataStream* stream);
+
+  // Returns the stream id for a new stream.
+  QuicStreamId GetNextStreamId();
+
+  QuicDataStream* GetIncomingDataStream(QuicStreamId stream_id);
+
+  QuicDataStream* GetDataStream(const QuicStreamId stream_id);
+
+  ReliableQuicStream* GetStream(const QuicStreamId stream_id);
+
+  // This is called after every call other than OnConnectionClose from the
+  // QuicConnectionVisitor to allow post-processing once the work has been done.
+  // In this case, it deletes streams given that it's safe to do so (no other
+  // operations are being done on the streams at this time)
+  virtual void PostProcessAfterData();
+
+  base::hash_map<QuicStreamId, QuicDataStream*>* streams() {
+    return &stream_map_;
+  }
+
+  const base::hash_map<QuicStreamId, QuicDataStream*>* streams() const {
+    return &stream_map_;
+  }
+
+  std::vector<QuicDataStream*>* closed_streams() { return &closed_streams_; }
+
+  void set_max_open_streams(size_t max_open_streams);
+
+  scoped_ptr<QuicHeadersStream> headers_stream_;
+
+ private:
+  friend class test::QuicSessionPeer;
+  friend class VisitorShim;
+
+  // Performs the work required to close |stream_id|.  If |locally_reset|
+  // then the stream has been reset by this endpoint, not by the peer.
+  void CloseStreamInner(QuicStreamId stream_id, bool locally_reset);
+
+  // When a stream is closed locally, it may not yet know how many bytes the
+  // peer sent on that stream.
+  // When this data arrives (via stream frame w. FIN, or RST) this method
+  // is called, and correctly updates the connection level flow controller.
+  void UpdateFlowControlOnFinalReceivedByteOffset(
+      QuicStreamId id, QuicStreamOffset final_byte_offset);
+
+  // Called in OnConfigNegotiated when we receive a new stream level flow
+  // control window in a negotiated config. Closes the connection if invalid.
+  void OnNewStreamFlowControlWindow(uint32 new_window);
+
+  // Called in OnConfigNegotiated when we receive a new session level flow
+  // control window in a negotiated config. Closes the connection if invalid.
+  void OnNewSessionFlowControlWindow(uint32 new_window);
+
+  // Keep track of highest received byte offset of locally closed streams, while
+  // waiting for a definitive final highest offset from the peer.
+  std::map<QuicStreamId, QuicStreamOffset>
+      locally_closed_streams_highest_offset_;
+
+  scoped_ptr<QuicConnection> connection_;
+
+  // A shim to stand between the connection and the session, to handle stream
+  // deletions.
+  scoped_ptr<VisitorShim> visitor_shim_;
+
+  std::vector<QuicDataStream*> closed_streams_;
+
+  QuicConfig config_;
+
+  // Returns the maximum number of streams this connection can open.
+  size_t max_open_streams_;
+
+  // Map from StreamId to pointers to streams that are owned by the caller.
+  DataStreamMap stream_map_;
+  QuicStreamId next_stream_id_;
+
+  // Set of stream ids that have been "implicitly created" by receipt
+  // of a stream id larger than the next expected stream id.
+  base::hash_set<QuicStreamId> implicitly_created_streams_;
+
+  // A list of streams which need to write more data.
+  QuicWriteBlockedList write_blocked_streams_;
+
+  QuicStreamId largest_peer_created_stream_id_;
+
+  // The latched error with which the connection was closed.
+  QuicErrorCode error_;
+
+  // Whether a GoAway has been received.
+  bool goaway_received_;
+  // Whether a GoAway has been sent.
+  bool goaway_sent_;
+
+  // Indicate if there is pending data for the crypto stream.
+  bool has_pending_handshake_;
+
+  // Used for session level flow control.
+  scoped_ptr<QuicFlowController> flow_controller_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSession);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SESSION_H_
diff --git a/net/quic/quic_session_test.cc b/net/quic/quic_session_test.cc
new file mode 100644
index 0000000..a36b77c
--- /dev/null
+++ b/net/quic/quic_session_test.cc
@@ -0,0 +1,954 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_session.h"
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_flags.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/quic/test_tools/quic_config_peer.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_data_stream_peer.h"
+#include "net/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::hash_map;
+using std::set;
+using std::vector;
+using testing::CreateFunctor;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const QuicPriority kHighestPriority = 0;
+const QuicPriority kSomeMiddlePriority = 3;
+
+class TestCryptoStream : public QuicCryptoStream {
+ public:
+  explicit TestCryptoStream(QuicSession* session)
+      : QuicCryptoStream(session) {
+  }
+
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE {
+    encryption_established_ = true;
+    handshake_confirmed_ = true;
+    CryptoHandshakeMessage msg;
+    string error_details;
+    session()->config()->SetInitialFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    session()->config()->SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    session()->config()->SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    session()->config()->ToHandshakeMessage(&msg);
+    const QuicErrorCode error = session()->config()->ProcessPeerHello(
+        msg, CLIENT, &error_details);
+    EXPECT_EQ(QUIC_NO_ERROR, error);
+    session()->OnConfigNegotiated();
+    session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+  }
+
+  MOCK_METHOD0(OnCanWrite, void());
+};
+
+class TestHeadersStream : public QuicHeadersStream {
+ public:
+  explicit TestHeadersStream(QuicSession* session)
+      : QuicHeadersStream(session) {
+  }
+
+  MOCK_METHOD0(OnCanWrite, void());
+};
+
+class TestStream : public QuicDataStream {
+ public:
+  TestStream(QuicStreamId id, QuicSession* session)
+      : QuicDataStream(id, session) {
+  }
+
+  using ReliableQuicStream::CloseWriteSide;
+
+  virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE {
+    return data_len;
+  }
+
+  void SendBody(const string& data, bool fin) {
+    WriteOrBufferData(data, fin, nullptr);
+  }
+
+  MOCK_METHOD0(OnCanWrite, void());
+};
+
+// Poor man's functor for use as callback in a mock.
+class StreamBlocker {
+ public:
+  StreamBlocker(QuicSession* session, QuicStreamId stream_id)
+      : session_(session),
+        stream_id_(stream_id) {
+  }
+
+  void MarkWriteBlocked() {
+    session_->MarkWriteBlocked(stream_id_, kSomeMiddlePriority);
+  }
+
+ private:
+  QuicSession* const session_;
+  const QuicStreamId stream_id_;
+};
+
+class TestSession : public QuicSession {
+ public:
+  explicit TestSession(QuicConnection* connection)
+      : QuicSession(connection,
+                    DefaultQuicConfig()),
+        crypto_stream_(this),
+        writev_consumes_all_data_(false) {
+    InitializeSession();
+  }
+
+  virtual TestCryptoStream* GetCryptoStream() OVERRIDE {
+    return &crypto_stream_;
+  }
+
+  virtual TestStream* CreateOutgoingDataStream() OVERRIDE {
+    TestStream* stream = new TestStream(GetNextStreamId(), this);
+    ActivateStream(stream);
+    return stream;
+  }
+
+  virtual TestStream* CreateIncomingDataStream(QuicStreamId id) OVERRIDE {
+    return new TestStream(id, this);
+  }
+
+  bool IsClosedStream(QuicStreamId id) {
+    return QuicSession::IsClosedStream(id);
+  }
+
+  QuicDataStream* GetIncomingDataStream(QuicStreamId stream_id) {
+    return QuicSession::GetIncomingDataStream(stream_id);
+  }
+
+  virtual QuicConsumedData WritevData(
+      QuicStreamId id,
+      const IOVector& data,
+      QuicStreamOffset offset,
+      bool fin,
+      FecProtection fec_protection,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate) OVERRIDE {
+    // Always consumes everything.
+    if (writev_consumes_all_data_) {
+      return QuicConsumedData(data.TotalBufferSize(), fin);
+    } else {
+      return QuicSession::WritevData(id, data, offset, fin, fec_protection,
+                                     ack_notifier_delegate);
+    }
+  }
+
+  void set_writev_consumes_all_data(bool val) {
+    writev_consumes_all_data_ = val;
+  }
+
+  QuicConsumedData SendStreamData(QuicStreamId id) {
+    return WritevData(id, IOVector(), 0, true, MAY_FEC_PROTECT, nullptr);
+  }
+
+  using QuicSession::PostProcessAfterData;
+
+ private:
+  StrictMock<TestCryptoStream> crypto_stream_;
+
+  bool writev_consumes_all_data_;
+};
+
+class QuicSessionTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+  QuicSessionTest()
+      : connection_(new MockConnection(true, SupportedVersions(GetParam()))),
+        session_(connection_) {
+    session_.config()->SetInitialFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    session_.config()->SetInitialStreamFlowControlWindowToSend(
+        kInitialStreamFlowControlWindowForTest);
+    session_.config()->SetInitialSessionFlowControlWindowToSend(
+        kInitialSessionFlowControlWindowForTest);
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "http";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+  }
+
+  void CheckClosedStreams() {
+    for (int i = kCryptoStreamId; i < 100; i++) {
+      if (closed_streams_.count(i) == 0) {
+        EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i;
+      } else {
+        EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i;
+      }
+    }
+  }
+
+  void CloseStream(QuicStreamId id) {
+    session_.CloseStream(id);
+    closed_streams_.insert(id);
+  }
+
+  QuicVersion version() const { return connection_->version(); }
+
+  MockConnection* connection_;
+  TestSession session_;
+  set<QuicStreamId> closed_streams_;
+  SpdyHeaderBlock headers_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests, QuicSessionTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicSessionTest, PeerAddress) {
+  EXPECT_EQ(IPEndPoint(Loopback4(), kTestPort), session_.peer_address());
+}
+
+TEST_P(QuicSessionTest, IsCryptoHandshakeConfirmed) {
+  EXPECT_FALSE(session_.IsCryptoHandshakeConfirmed());
+  CryptoHandshakeMessage message;
+  session_.GetCryptoStream()->OnHandshakeMessage(message);
+  EXPECT_TRUE(session_.IsCryptoHandshakeConfirmed());
+}
+
+TEST_P(QuicSessionTest, IsClosedStreamDefault) {
+  // Ensure that no streams are initially closed.
+  for (int i = kCryptoStreamId; i < 100; i++) {
+    EXPECT_FALSE(session_.IsClosedStream(i)) << "stream id: " << i;
+  }
+}
+
+TEST_P(QuicSessionTest, ImplicitlyCreatedStreams) {
+  ASSERT_TRUE(session_.GetIncomingDataStream(7) != nullptr);
+  // Both 3 and 5 should be implicitly created.
+  EXPECT_FALSE(session_.IsClosedStream(3));
+  EXPECT_FALSE(session_.IsClosedStream(5));
+  ASSERT_TRUE(session_.GetIncomingDataStream(5) != nullptr);
+  ASSERT_TRUE(session_.GetIncomingDataStream(3) != nullptr);
+}
+
+TEST_P(QuicSessionTest, IsClosedStreamLocallyCreated) {
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  EXPECT_EQ(2u, stream2->id());
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  EXPECT_EQ(4u, stream4->id());
+
+  CheckClosedStreams();
+  CloseStream(4);
+  CheckClosedStreams();
+  CloseStream(2);
+  CheckClosedStreams();
+}
+
+TEST_P(QuicSessionTest, IsClosedStreamPeerCreated) {
+  QuicStreamId stream_id1 = kClientDataStreamId1;
+  QuicStreamId stream_id2 = kClientDataStreamId2;
+  QuicDataStream* stream1 = session_.GetIncomingDataStream(stream_id1);
+  QuicDataStreamPeer::SetHeadersDecompressed(stream1, true);
+  QuicDataStream* stream2 = session_.GetIncomingDataStream(stream_id2);
+  QuicDataStreamPeer::SetHeadersDecompressed(stream2, true);
+
+  CheckClosedStreams();
+  CloseStream(stream_id1);
+  CheckClosedStreams();
+  CloseStream(stream_id2);
+  // Create a stream explicitly, and another implicitly.
+  QuicDataStream* stream3 = session_.GetIncomingDataStream(stream_id2 + 4);
+  QuicDataStreamPeer::SetHeadersDecompressed(stream3, true);
+  CheckClosedStreams();
+  // Close one, but make sure the other is still not closed
+  CloseStream(stream3->id());
+  CheckClosedStreams();
+}
+
+TEST_P(QuicSessionTest, StreamIdTooLarge) {
+  QuicStreamId stream_id = kClientDataStreamId1;
+  session_.GetIncomingDataStream(stream_id);
+  EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID));
+  session_.GetIncomingDataStream(stream_id + kMaxStreamIdDelta + 2);
+}
+
+TEST_P(QuicSessionTest, DecompressionError) {
+  QuicHeadersStream* stream = QuicSessionPeer::GetHeadersStream(&session_);
+  const unsigned char data[] = {
+    0x80, 0x03, 0x00, 0x01,  // SPDY/3 SYN_STREAM frame
+    0x00, 0x00, 0x00, 0x25,  // flags/length
+    0x00, 0x00, 0x00, 0x05,  // stream id
+    0x00, 0x00, 0x00, 0x00,  // associated stream id
+    0x00, 0x00,
+    'a',  'b',  'c',  'd'    // invalid compressed data
+  };
+  EXPECT_CALL(*connection_,
+              SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+                                             "SPDY framing error."));
+  stream->ProcessRawData(reinterpret_cast<const char*>(data),
+                         arraysize(data));
+}
+
+TEST_P(QuicSessionTest, DebugDFatalIfMarkingClosedStreamWriteBlocked) {
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  // Close the stream.
+  stream2->Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+  // TODO(rtenneti): enable when chromium supports EXPECT_DEBUG_DFATAL.
+  /*
+  QuicStreamId kClosedStreamId = stream2->id();
+  EXPECT_DEBUG_DFATAL(
+      session_.MarkWriteBlocked(kClosedStreamId, kSomeMiddlePriority),
+      "Marking unknown stream 2 blocked.");
+  */
+}
+
+TEST_P(QuicSessionTest, DebugDFatalIfMarkWriteBlockedCalledWithWrongPriority) {
+  const QuicPriority kDifferentPriority = 0;
+
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  EXPECT_NE(kDifferentPriority, stream2->EffectivePriority());
+  // TODO(rtenneti): enable when chromium supports EXPECT_DEBUG_DFATAL.
+  /*
+  EXPECT_DEBUG_DFATAL(
+      session_.MarkWriteBlocked(stream2->id(), kDifferentPriority),
+      "Priorities do not match.  Got: 0 Expected: 3");
+  */
+}
+
+TEST_P(QuicSessionTest, OnCanWrite) {
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  TestStream* stream6 = session_.CreateOutgoingDataStream();
+
+  session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority);
+
+  InSequence s;
+  StreamBlocker stream2_blocker(&session_, stream2->id());
+  // Reregister, to test the loop limit.
+  EXPECT_CALL(*stream2, OnCanWrite())
+      .WillOnce(Invoke(&stream2_blocker, &StreamBlocker::MarkWriteBlocked));
+  EXPECT_CALL(*stream6, OnCanWrite());
+  EXPECT_CALL(*stream4, OnCanWrite());
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSessionTest, OnCanWriteBundlesStreams) {
+  // Drive congestion control manually.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  TestStream* stream6 = session_.CreateOutgoingDataStream();
+
+  session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority);
+
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillRepeatedly(
+      Return(QuicTime::Delta::Zero()));
+  EXPECT_CALL(*send_algorithm, GetCongestionWindow())
+      .WillOnce(Return(kMaxPacketSize * 10));
+  EXPECT_CALL(*stream2, OnCanWrite())
+      .WillOnce(IgnoreResult(Invoke(CreateFunctor(
+          &session_, &TestSession::SendStreamData, stream2->id()))));
+  EXPECT_CALL(*stream4, OnCanWrite())
+      .WillOnce(IgnoreResult(Invoke(CreateFunctor(
+          &session_, &TestSession::SendStreamData, stream4->id()))));
+  EXPECT_CALL(*stream6, OnCanWrite())
+      .WillOnce(IgnoreResult(Invoke(CreateFunctor(
+          &session_, &TestSession::SendStreamData, stream6->id()))));
+
+  // Expect that we only send one packet, the writes from different streams
+  // should be bundled together.
+  MockPacketWriter* writer =
+      static_cast<MockPacketWriter*>(
+          QuicConnectionPeer::GetWriter(session_.connection()));
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _)).WillOnce(
+                  Return(WriteResult(WRITE_STATUS_OK, 0)));
+  EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _)).Times(1);
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSessionTest, OnCanWriteCongestionControlBlocks) {
+  InSequence s;
+
+  // Drive congestion control manually.
+  MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+  QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
+
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  TestStream* stream6 = session_.CreateOutgoingDataStream();
+
+  session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority);
+
+  StreamBlocker stream2_blocker(&session_, stream2->id());
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Zero()));
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Zero()));
+  EXPECT_CALL(*stream6, OnCanWrite());
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  // stream4->OnCanWrite is not called.
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // Still congestion-control blocked.
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Infinite()));
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+
+  // stream4->OnCanWrite is called once the connection stops being
+  // congestion-control blocked.
+  EXPECT_CALL(*send_algorithm, TimeUntilSend(_, _, _)).WillOnce(Return(
+      QuicTime::Delta::Zero()));
+  EXPECT_CALL(*stream4, OnCanWrite());
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSessionTest, BufferedHandshake) {
+  EXPECT_FALSE(session_.HasPendingHandshake());  // Default value.
+
+  // Test that blocking other streams does not change our status.
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  StreamBlocker stream2_blocker(&session_, stream2->id());
+  stream2_blocker.MarkWriteBlocked();
+  EXPECT_FALSE(session_.HasPendingHandshake());
+
+  TestStream* stream3 = session_.CreateOutgoingDataStream();
+  StreamBlocker stream3_blocker(&session_, stream3->id());
+  stream3_blocker.MarkWriteBlocked();
+  EXPECT_FALSE(session_.HasPendingHandshake());
+
+  // Blocking (due to buffering of) the Crypto stream is detected.
+  session_.MarkWriteBlocked(kCryptoStreamId, kHighestPriority);
+  EXPECT_TRUE(session_.HasPendingHandshake());
+
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  StreamBlocker stream4_blocker(&session_, stream4->id());
+  stream4_blocker.MarkWriteBlocked();
+  EXPECT_TRUE(session_.HasPendingHandshake());
+
+  InSequence s;
+  // Force most streams to re-register, which is common scenario when we block
+  // the Crypto stream, and only the crypto stream can "really" write.
+
+  // Due to prioritization, we *should* be asked to write the crypto stream
+  // first.
+  // Don't re-register the crypto stream (which signals complete writing).
+  TestCryptoStream* crypto_stream = session_.GetCryptoStream();
+  EXPECT_CALL(*crypto_stream, OnCanWrite());
+
+  // Re-register all other streams, to show they weren't able to proceed.
+  EXPECT_CALL(*stream2, OnCanWrite())
+      .WillOnce(Invoke(&stream2_blocker, &StreamBlocker::MarkWriteBlocked));
+  EXPECT_CALL(*stream3, OnCanWrite())
+      .WillOnce(Invoke(&stream3_blocker, &StreamBlocker::MarkWriteBlocked));
+  EXPECT_CALL(*stream4, OnCanWrite())
+      .WillOnce(Invoke(&stream4_blocker, &StreamBlocker::MarkWriteBlocked));
+
+  session_.OnCanWrite();
+  EXPECT_TRUE(session_.WillingAndAbleToWrite());
+  EXPECT_FALSE(session_.HasPendingHandshake());  // Crypto stream wrote.
+}
+
+TEST_P(QuicSessionTest, OnCanWriteWithClosedStream) {
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  TestStream* stream4 = session_.CreateOutgoingDataStream();
+  TestStream* stream6 = session_.CreateOutgoingDataStream();
+
+  session_.MarkWriteBlocked(stream2->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream6->id(), kSomeMiddlePriority);
+  session_.MarkWriteBlocked(stream4->id(), kSomeMiddlePriority);
+  CloseStream(stream6->id());
+
+  InSequence s;
+  EXPECT_CALL(*stream2, OnCanWrite());
+  EXPECT_CALL(*stream4, OnCanWrite());
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSessionTest, OnCanWriteLimitsNumWritesIfFlowControlBlocked) {
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Ensure connection level flow control blockage.
+  QuicFlowControllerPeer::SetSendWindowOffset(session_.flow_controller(), 0);
+  EXPECT_TRUE(session_.flow_controller()->IsBlocked());
+
+  // Mark the crypto and headers streams as write blocked, we expect them to be
+  // allowed to write later.
+  session_.MarkWriteBlocked(kCryptoStreamId, kHighestPriority);
+  session_.MarkWriteBlocked(kHeadersStreamId, kHighestPriority);
+
+  // Create a data stream, and although it is write blocked we never expect it
+  // to be allowed to write as we are connection level flow control blocked.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+  session_.MarkWriteBlocked(stream->id(), kSomeMiddlePriority);
+  EXPECT_CALL(*stream, OnCanWrite()).Times(0);
+
+  // The crypto and headers streams should be called even though we are
+  // connection flow control blocked.
+  TestCryptoStream* crypto_stream = session_.GetCryptoStream();
+  EXPECT_CALL(*crypto_stream, OnCanWrite()).Times(1);
+  TestHeadersStream* headers_stream = new TestHeadersStream(&session_);
+  QuicSessionPeer::SetHeadersStream(&session_, headers_stream);
+  EXPECT_CALL(*headers_stream, OnCanWrite()).Times(1);
+
+  session_.OnCanWrite();
+  EXPECT_FALSE(session_.WillingAndAbleToWrite());
+}
+
+TEST_P(QuicSessionTest, SendGoAway) {
+  EXPECT_CALL(*connection_,
+              SendGoAway(QUIC_PEER_GOING_AWAY, 0u, "Going Away."));
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+  EXPECT_TRUE(session_.goaway_sent());
+
+  EXPECT_CALL(*connection_,
+              SendRstStream(3u, QUIC_STREAM_PEER_GOING_AWAY, 0)).Times(0);
+  EXPECT_TRUE(session_.GetIncomingDataStream(3u));
+}
+
+TEST_P(QuicSessionTest, DoNotSendGoAwayTwice) {
+  EXPECT_CALL(*connection_,
+              SendGoAway(QUIC_PEER_GOING_AWAY, 0u, "Going Away.")).Times(1);
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+  EXPECT_TRUE(session_.goaway_sent());
+  session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+}
+
+TEST_P(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
+  EXPECT_EQ((FLAGS_quic_unified_timeouts ?
+             kInitialIdleTimeoutSecs : kDefaultIdleTimeoutSecs) + 1,
+            QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+  CryptoHandshakeMessage msg;
+  session_.GetCryptoStream()->OnHandshakeMessage(msg);
+  EXPECT_EQ(kMaximumIdleTimeoutSecs + 1,
+            QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+}
+
+TEST_P(QuicSessionTest, RstStreamBeforeHeadersDecompressed) {
+  // Send two bytes of payload.
+  QuicStreamFrame data1(kClientDataStreamId1, false, 0, MakeIOVector("HT"));
+  vector<QuicStreamFrame> frames;
+  frames.push_back(data1);
+  session_.OnStreamFrames(frames);
+  EXPECT_EQ(1u, session_.GetNumOpenStreams());
+
+  QuicRstStreamFrame rst1(kClientDataStreamId1, QUIC_STREAM_NO_ERROR, 0);
+  session_.OnRstStream(rst1);
+  EXPECT_EQ(0u, session_.GetNumOpenStreams());
+  // Connection should remain alive.
+  EXPECT_TRUE(connection_->connected());
+}
+
+TEST_P(QuicSessionTest, MultipleRstStreamsCauseSingleConnectionClose) {
+  // If multiple invalid reset stream frames arrive in a single packet, this
+  // should trigger a connection close. However there is no need to send
+  // multiple connection close frames.
+
+  // Create valid stream.
+  QuicStreamFrame data1(kClientDataStreamId1, false, 0, MakeIOVector("HT"));
+  vector<QuicStreamFrame> frames;
+  frames.push_back(data1);
+  session_.OnStreamFrames(frames);
+  EXPECT_EQ(1u, session_.GetNumOpenStreams());
+
+  // Process first invalid stream reset, resulting in the connection being
+  // closed.
+  EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID))
+      .Times(1);
+  QuicStreamId kLargeInvalidStreamId = 99999999;
+  QuicRstStreamFrame rst1(kLargeInvalidStreamId, QUIC_STREAM_NO_ERROR, 0);
+  session_.OnRstStream(rst1);
+  QuicConnectionPeer::CloseConnection(connection_);
+
+  // Processing of second invalid stream reset should not result in the
+  // connection being closed for a second time.
+  QuicRstStreamFrame rst2(kLargeInvalidStreamId, QUIC_STREAM_NO_ERROR, 0);
+  session_.OnRstStream(rst2);
+}
+
+TEST_P(QuicSessionTest, HandshakeUnblocksFlowControlBlockedStream) {
+  // Test that if a stream is flow control blocked, then on receipt of the SHLO
+  // containing a suitable send window offset, the stream becomes unblocked.
+
+  // Ensure that Writev consumes all the data it is given (simulate no socket
+  // blocking).
+  session_.set_writev_consumes_all_data(true);
+
+  // Create a stream, and send enough data to make it flow control blocked.
+  TestStream* stream2 = session_.CreateOutgoingDataStream();
+  string body(kDefaultFlowControlSendWindow, '.');
+  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+  stream2->SendBody(body, false);
+  EXPECT_TRUE(stream2->flow_controller()->IsBlocked());
+
+  // Now complete the crypto handshake, resulting in an increased flow control
+  // send window.
+  CryptoHandshakeMessage msg;
+  session_.GetCryptoStream()->OnHandshakeMessage(msg);
+
+  // Stream is now unblocked.
+  EXPECT_FALSE(stream2->flow_controller()->IsBlocked());
+}
+
+TEST_P(QuicSessionTest, InvalidFlowControlWindowInHandshake) {
+  // TODO(rjshade): Remove this test when removing QUIC_VERSION_19.
+  // Test that receipt of an invalid (< default) flow control window from
+  // the peer results in the connection being torn down.
+  if (version() > QUIC_VERSION_19) {
+    return;
+  }
+
+  uint32 kInvalidWindow = kDefaultFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialFlowControlWindow(session_.config(),
+                                                      kInvalidWindow);
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_INVALID_WINDOW)).Times(2);
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTest, InvalidStreamFlowControlWindowInHandshake) {
+  // Test that receipt of an invalid (< default) stream flow control window from
+  // the peer results in the connection being torn down.
+  if (version() <= QUIC_VERSION_19) {
+    return;
+  }
+
+  uint32 kInvalidWindow = kDefaultFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(session_.config(),
+                                                            kInvalidWindow);
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_INVALID_WINDOW));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTest, InvalidSessionFlowControlWindowInHandshake) {
+  // Test that receipt of an invalid (< default) session flow control window
+  // from the peer results in the connection being torn down.
+  if (version() <= QUIC_VERSION_19) {
+    return;
+  }
+
+  uint32 kInvalidWindow = kDefaultFlowControlSendWindow - 1;
+  QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(session_.config(),
+                                                             kInvalidWindow);
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_INVALID_WINDOW));
+  session_.OnConfigNegotiated();
+}
+
+TEST_P(QuicSessionTest, ConnectionFlowControlAccountingRstOutOfOrder) {
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Test that when we receive an out of order stream RST we correctly adjust
+  // our connection level flow control receive window.
+  // On close, the stream should mark as consumed all bytes between the highest
+  // byte consumed so far and the final byte offset from the RST frame.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+
+  const QuicStreamOffset kByteOffset =
+      1 + kInitialSessionFlowControlWindowForTest / 2;
+
+  // Expect no stream WINDOW_UPDATE frames, as stream read side closed.
+  EXPECT_CALL(*connection_, SendWindowUpdate(stream->id(), _)).Times(0);
+  // We do expect a connection level WINDOW_UPDATE when the stream is reset.
+  EXPECT_CALL(*connection_,
+              SendWindowUpdate(0, kInitialSessionFlowControlWindowForTest +
+                                      kByteOffset)).Times(1);
+
+  QuicRstStreamFrame rst_frame(stream->id(), QUIC_STREAM_CANCELLED,
+                               kByteOffset);
+  session_.OnRstStream(rst_frame);
+  session_.PostProcessAfterData();
+  EXPECT_EQ(kByteOffset, session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSessionTest, ConnectionFlowControlAccountingFinAndLocalReset) {
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Test the situation where we receive a FIN on a stream, and before we fully
+  // consume all the data from the sequencer buffer we locally RST the stream.
+  // The bytes between highest consumed byte, and the final byte offset that we
+  // determined when the FIN arrived, should be marked as consumed at the
+  // connection level flow controller when the stream is reset.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+
+  const QuicStreamOffset kByteOffset =
+      1 + kInitialSessionFlowControlWindowForTest / 2;
+  QuicStreamFrame frame(stream->id(), true, kByteOffset, IOVector());
+  vector<QuicStreamFrame> frames;
+  frames.push_back(frame);
+  session_.OnStreamFrames(frames);
+  session_.PostProcessAfterData();
+
+  EXPECT_EQ(0u, stream->flow_controller()->bytes_consumed());
+  EXPECT_EQ(kByteOffset,
+            stream->flow_controller()->highest_received_byte_offset());
+
+  // We only expect to see a connection WINDOW_UPDATE when talking
+  // QUIC_VERSION_19, as in this case both stream and session flow control
+  // windows are the same size. In later versions we will not see a connection
+  // level WINDOW_UPDATE when exhausting a stream, as the stream flow control
+  // limit is much lower than the connection flow control limit.
+  if (version() == QUIC_VERSION_19) {
+    // Expect no stream WINDOW_UPDATE frames, as stream read side closed.
+    EXPECT_CALL(*connection_, SendWindowUpdate(stream->id(), _)).Times(0);
+    // We do expect a connection level WINDOW_UPDATE when the stream is reset.
+    EXPECT_CALL(*connection_,
+                SendWindowUpdate(0, kInitialSessionFlowControlWindowForTest +
+                                        kByteOffset)).Times(1);
+  }
+
+  // Reset stream locally.
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  EXPECT_EQ(kByteOffset, session_.flow_controller()->bytes_consumed());
+}
+
+TEST_P(QuicSessionTest, ConnectionFlowControlAccountingFinAfterRst) {
+  // Test that when we RST the stream (and tear down stream state), and then
+  // receive a FIN from the peer, we correctly adjust our connection level flow
+  // control receive window.
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Connection starts with some non-zero highest received byte offset,
+  // due to other active streams.
+  const uint64 kInitialConnectionBytesConsumed = 567;
+  const uint64 kInitialConnectionHighestReceivedOffset = 1234;
+  EXPECT_LT(kInitialConnectionBytesConsumed,
+            kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->UpdateHighestReceivedOffset(
+      kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+  // Reset our stream: this results in the stream being closed locally.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+  stream->Reset(QUIC_STREAM_CANCELLED);
+
+  // Now receive a response from the peer with a FIN. We should handle this by
+  // adjusting the connection level flow control receive window to take into
+  // account the total number of bytes sent by the peer.
+  const QuicStreamOffset kByteOffset = 5678;
+  string body = "hello";
+  IOVector data = MakeIOVector(body);
+  QuicStreamFrame frame(stream->id(), true, kByteOffset, data);
+  vector<QuicStreamFrame> frames;
+  frames.push_back(frame);
+  session_.OnStreamFrames(frames);
+
+  QuicStreamOffset total_stream_bytes_sent_by_peer =
+      kByteOffset + body.length();
+  EXPECT_EQ(kInitialConnectionBytesConsumed + total_stream_bytes_sent_by_peer,
+            session_.flow_controller()->bytes_consumed());
+  EXPECT_EQ(
+      kInitialConnectionHighestReceivedOffset + total_stream_bytes_sent_by_peer,
+      session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSessionTest, ConnectionFlowControlAccountingRstAfterRst) {
+  // Test that when we RST the stream (and tear down stream state), and then
+  // receive a RST from the peer, we correctly adjust our connection level flow
+  // control receive window.
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Connection starts with some non-zero highest received byte offset,
+  // due to other active streams.
+  const uint64 kInitialConnectionBytesConsumed = 567;
+  const uint64 kInitialConnectionHighestReceivedOffset = 1234;
+  EXPECT_LT(kInitialConnectionBytesConsumed,
+            kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->UpdateHighestReceivedOffset(
+      kInitialConnectionHighestReceivedOffset);
+  session_.flow_controller()->AddBytesConsumed(kInitialConnectionBytesConsumed);
+
+  // Reset our stream: this results in the stream being closed locally.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+  stream->Reset(QUIC_STREAM_CANCELLED);
+
+  // Now receive a RST from the peer. We should handle this by adjusting the
+  // connection level flow control receive window to take into account the total
+  // number of bytes sent by the peer.
+  const QuicStreamOffset kByteOffset = 5678;
+  QuicRstStreamFrame rst_frame(stream->id(), QUIC_STREAM_CANCELLED,
+                               kByteOffset);
+  session_.OnRstStream(rst_frame);
+
+  EXPECT_EQ(kInitialConnectionBytesConsumed + kByteOffset,
+            session_.flow_controller()->bytes_consumed());
+  EXPECT_EQ(kInitialConnectionHighestReceivedOffset + kByteOffset,
+            session_.flow_controller()->highest_received_byte_offset());
+}
+
+TEST_P(QuicSessionTest, FlowControlWithInvalidFinalOffset) {
+  // Test that if we receive a stream RST with a highest byte offset that
+  // violates flow control, that we close the connection.
+  const uint64 kLargeOffset = kInitialSessionFlowControlWindowForTest + 1;
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA))
+      .Times(2);
+
+  // Check that stream frame + FIN results in connection close.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+  stream->Reset(QUIC_STREAM_CANCELLED);
+  QuicStreamFrame frame(stream->id(), true, kLargeOffset, IOVector());
+  vector<QuicStreamFrame> frames;
+  frames.push_back(frame);
+  session_.OnStreamFrames(frames);
+
+  // Check that RST results in connection close.
+  QuicRstStreamFrame rst_frame(stream->id(), QUIC_STREAM_CANCELLED,
+                               kLargeOffset);
+  session_.OnRstStream(rst_frame);
+}
+
+TEST_P(QuicSessionTest, VersionNegotiationDisablesFlowControl) {
+  if (version() < QUIC_VERSION_19) {
+    return;
+  }
+
+  // Test that after successful version negotiation, flow control is disabled
+  // appropriately at both the connection and stream level.
+
+  // Initially both stream and connection flow control are enabled.
+  TestStream* stream = session_.CreateOutgoingDataStream();
+  EXPECT_TRUE(stream->flow_controller()->IsEnabled());
+  EXPECT_TRUE(session_.flow_controller()->IsEnabled());
+
+  // Version 18 implies that stream flow control is enabled, but connection
+  // level is disabled.
+  session_.OnSuccessfulVersionNegotiation(QUIC_VERSION_18);
+  EXPECT_FALSE(session_.flow_controller()->IsEnabled());
+  EXPECT_TRUE(stream->flow_controller()->IsEnabled());
+}
+
+TEST_P(QuicSessionTest, WindowUpdateUnblocksHeadersStream) {
+  // Test that a flow control blocked headers stream gets unblocked on recipt of
+  // a WINDOW_UPDATE frame. Regression test for b/17413860.
+  if (version() < QUIC_VERSION_21) {
+    return;
+  }
+
+  // Set the headers stream to be flow control blocked.
+  QuicHeadersStream* headers_stream =
+      QuicSessionPeer::GetHeadersStream(&session_);
+  QuicFlowControllerPeer::SetSendWindowOffset(headers_stream->flow_controller(),
+                                              0);
+  EXPECT_TRUE(headers_stream->flow_controller()->IsBlocked());
+
+  // Unblock the headers stream by supplying a WINDOW_UPDATE.
+  QuicWindowUpdateFrame window_update_frame(headers_stream->id(),
+                                            2 * kDefaultFlowControlSendWindow);
+  vector<QuicWindowUpdateFrame> frames;
+  frames.push_back(window_update_frame);
+  session_.OnWindowUpdateFrames(frames);
+  EXPECT_FALSE(headers_stream->flow_controller()->IsBlocked());
+}
+
+TEST_P(QuicSessionTest, TooManyUnfinishedStreamsCauseConnectionClose) {
+  if (version() < QUIC_VERSION_18) {
+    return;
+  }
+  // If a buggy/malicious peer creates too many streams that are not ended with
+  // a FIN or RST then we send a connection close.
+  ValueRestore<bool> old_flag(&FLAGS_close_quic_connection_unfinished_streams_2,
+                              true);
+
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_TOO_MANY_UNFINISHED_STREAMS)).Times(1);
+
+  const int kMaxStreams = 5;
+  QuicSessionPeer::SetMaxOpenStreams(&session_, kMaxStreams);
+
+  // Create kMaxStreams + 1 data streams, and close them all without receiving a
+  // FIN or a RST from the client.
+  const int kFirstStreamId = kClientDataStreamId1;
+  const int kFinalStreamId = kClientDataStreamId1 + 2 * kMaxStreams + 1;
+  for (int i = kFirstStreamId; i < kFinalStreamId; i += 2) {
+    QuicStreamFrame data1(i, false, 0, MakeIOVector("HT"));
+    vector<QuicStreamFrame> frames;
+    frames.push_back(data1);
+    session_.OnStreamFrames(frames);
+    EXPECT_EQ(1u, session_.GetNumOpenStreams());
+    session_.CloseStream(i);
+  }
+
+  // Called after any new data is received by the session, and triggers the call
+  // to close the connection.
+  session_.PostProcessAfterData();
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_socket_address_coder.cc b/net/quic/quic_socket_address_coder.cc
new file mode 100644
index 0000000..77e595f
--- /dev/null
+++ b/net/quic/quic_socket_address_coder.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_socket_address_coder.h"
+
+using std::string;
+
+namespace net {
+
+namespace {
+
+// For convenience, the values of these constants match the values of AF_INET
+// and AF_INET6 on Linux.
+const uint16 kIPv4 = 2;
+const uint16 kIPv6 = 10;
+
+}  // namespace
+
+QuicSocketAddressCoder::QuicSocketAddressCoder() {
+}
+
+QuicSocketAddressCoder::QuicSocketAddressCoder(const IPEndPoint& address)
+    : address_(address) {
+}
+
+QuicSocketAddressCoder::~QuicSocketAddressCoder() {
+}
+
+string QuicSocketAddressCoder::Encode() const {
+  string serialized;
+  uint16 address_family;
+  switch (address_.GetSockAddrFamily()) {
+    case AF_INET:
+      address_family = kIPv4;
+      break;
+    case AF_INET6:
+      address_family = kIPv6;
+      break;
+    default:
+      return serialized;
+  }
+  serialized.append(reinterpret_cast<const char*>(&address_family),
+                    sizeof(address_family));
+  serialized.append(IPAddressToPackedString(address_.address()));
+  uint16 port = address_.port();
+  serialized.append(reinterpret_cast<const char*>(&port), sizeof(port));
+  return serialized;
+}
+
+bool QuicSocketAddressCoder::Decode(const char* data, size_t length) {
+  uint16 address_family;
+  if (length < sizeof(address_family)) {
+    return false;
+  }
+  memcpy(&address_family, data, sizeof(address_family));
+  data += sizeof(address_family);
+  length -= sizeof(address_family);
+
+  size_t ip_length;
+  switch (address_family) {
+    case kIPv4:
+      ip_length = kIPv4AddressSize;
+      break;
+    case kIPv6:
+      ip_length = kIPv6AddressSize;
+      break;
+    default:
+      return false;
+  }
+  if (length < ip_length) {
+    return false;
+  }
+  IPAddressNumber ip(ip_length);
+  memcpy(&ip[0], data, ip_length);
+  data += ip_length;
+  length -= ip_length;
+
+  uint16 port;
+  if (length != sizeof(port)) {
+    return false;
+  }
+  memcpy(&port, data, length);
+
+  address_ = IPEndPoint(ip, port);
+  return true;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_socket_address_coder.h b/net/quic/quic_socket_address_coder.h
new file mode 100644
index 0000000..36ad1d0
--- /dev/null
+++ b/net/quic/quic_socket_address_coder.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SOCKET_ADDRESS_CODER_H_
+#define NET_QUIC_QUIC_SOCKET_ADDRESS_CODER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Serializes and parses a socket address (IP address and port), to be used in
+// the kCADR tag in the ServerHello handshake message and the Public Reset
+// packet.
+class NET_EXPORT_PRIVATE QuicSocketAddressCoder {
+ public:
+  QuicSocketAddressCoder();
+  explicit QuicSocketAddressCoder(const IPEndPoint& address);
+  ~QuicSocketAddressCoder();
+
+  std::string Encode() const;
+
+  bool Decode(const char* data, size_t length);
+
+  IPAddressNumber ip() const {
+    return address_.address();
+  }
+
+  uint16 port() const {
+    return address_.port();
+  }
+
+ private:
+  IPEndPoint address_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSocketAddressCoder);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SOCKET_ADDRESS_CODER_H_
diff --git a/net/quic/quic_socket_address_coder_test.cc b/net/quic/quic_socket_address_coder_test.cc
new file mode 100644
index 0000000..56bed58
--- /dev/null
+++ b/net/quic/quic_socket_address_coder_test.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_socket_address_coder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+
+TEST(QuicSocketAddressCoderTest, EncodeIPv4) {
+  IPAddressNumber ip;
+  ASSERT_TRUE(ParseIPLiteralToNumber("4.31.198.44", &ip));
+  QuicSocketAddressCoder coder(IPEndPoint(ip, 0x1234));
+  string serialized = coder.Encode();
+  string expected("\x02\x00\x04\x1f\xc6\x2c\x34\x12", 8);
+  EXPECT_EQ(expected, serialized);
+}
+
+TEST(QuicSocketAddressCoderTest, EncodeIPv6) {
+  IPAddressNumber ip;
+  ASSERT_TRUE(ParseIPLiteralToNumber("2001:700:300:1800::f", &ip));
+  QuicSocketAddressCoder coder(IPEndPoint(ip, 0x5678));
+  string serialized = coder.Encode();
+  string expected("\x0a\x00"
+                  "\x20\x01\x07\x00\x03\x00\x18\x00"
+                  "\x00\x00\x00\x00\x00\x00\x00\x0f"
+                  "\x78\x56", 20);
+  EXPECT_EQ(expected, serialized);
+}
+
+TEST(QuicSocketAddressCoderTest, DecodeIPv4) {
+  string serialized("\x02\x00\x04\x1f\xc6\x2c\x34\x12", 8);
+  QuicSocketAddressCoder coder;
+  ASSERT_TRUE(coder.Decode(serialized.data(), serialized.length()));
+  EXPECT_EQ(AF_INET, ConvertAddressFamily(GetAddressFamily(coder.ip())));
+  string expected_addr("\x04\x1f\xc6\x2c", 4);
+  EXPECT_EQ(expected_addr, IPAddressToPackedString(coder.ip()));
+  EXPECT_EQ(0x1234, coder.port());
+}
+
+TEST(QuicSocketAddressCoderTest, DecodeIPv6) {
+  string serialized("\x0a\x00"
+                    "\x20\x01\x07\x00\x03\x00\x18\x00"
+                    "\x00\x00\x00\x00\x00\x00\x00\x0f"
+                    "\x78\x56", 20);
+  QuicSocketAddressCoder coder;
+  ASSERT_TRUE(coder.Decode(serialized.data(), serialized.length()));
+  EXPECT_EQ(AF_INET6, ConvertAddressFamily(GetAddressFamily(coder.ip())));
+  string expected_addr("\x20\x01\x07\x00\x03\x00\x18\x00"
+                       "\x00\x00\x00\x00\x00\x00\x00\x0f", 16);
+  EXPECT_EQ(expected_addr, IPAddressToPackedString(coder.ip()));
+  EXPECT_EQ(0x5678, coder.port());
+}
+
+TEST(QuicSocketAddressCoderTest, DecodeBad) {
+  string serialized("\x0a\x00"
+                    "\x20\x01\x07\x00\x03\x00\x18\x00"
+                    "\x00\x00\x00\x00\x00\x00\x00\x0f"
+                    "\x78\x56", 20);
+  QuicSocketAddressCoder coder;
+  EXPECT_TRUE(coder.Decode(serialized.data(), serialized.length()));
+  // Append junk.
+  serialized.push_back('\0');
+  EXPECT_FALSE(coder.Decode(serialized.data(), serialized.length()));
+  // Undo.
+  serialized.resize(20);
+  EXPECT_TRUE(coder.Decode(serialized.data(), serialized.length()));
+
+  // Set an unknown address family.
+  serialized[0] = '\x03';
+  EXPECT_FALSE(coder.Decode(serialized.data(), serialized.length()));
+  // Undo.
+  serialized[0] = '\x0a';
+  EXPECT_TRUE(coder.Decode(serialized.data(), serialized.length()));
+
+  // Truncate.
+  size_t len = serialized.length();
+  for (size_t i = 0; i < len; i++) {
+    ASSERT_FALSE(serialized.empty());
+    serialized.erase(serialized.length() - 1);
+    EXPECT_FALSE(coder.Decode(serialized.data(), serialized.length()));
+  }
+  EXPECT_TRUE(serialized.empty());
+}
+
+TEST(QuicSocketAddressCoderTest, EncodeAndDecode) {
+  struct {
+    const char* ip_literal;
+    uint16 port;
+  } test_case[] = {
+    { "93.184.216.119", 0x1234 },
+    { "199.204.44.194", 80 },
+    { "149.20.4.69", 443 },
+    { "127.0.0.1", 8080 },
+    { "2001:700:300:1800::", 0x5678 },
+    { "::1", 65534 },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_case); i++) {
+    IPAddressNumber ip;
+    ASSERT_TRUE(ParseIPLiteralToNumber(test_case[i].ip_literal, &ip));
+    QuicSocketAddressCoder encoder(IPEndPoint(ip, test_case[i].port));
+    string serialized = encoder.Encode();
+
+    QuicSocketAddressCoder decoder;
+    ASSERT_TRUE(decoder.Decode(serialized.data(), serialized.length()));
+    EXPECT_EQ(encoder.ip(), decoder.ip());
+    EXPECT_EQ(encoder.port(), decoder.port());
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_spdy_server_stream.cc b/net/quic/quic_spdy_server_stream.cc
new file mode 100644
index 0000000..b0f8abb
--- /dev/null
+++ b/net/quic/quic_spdy_server_stream.cc
@@ -0,0 +1,163 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_spdy_server_stream.h"
+
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_in_memory_cache.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/spdy_utils.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_http_utils.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+static const size_t kHeaderBufInitialSize = 4096;
+
+QuicSpdyServerStream::QuicSpdyServerStream(QuicStreamId id,
+                                           QuicSession* session)
+    : QuicDataStream(id, session),
+      read_buf_(new GrowableIOBuffer()),
+      request_headers_received_(false) {
+  read_buf_->SetCapacity(kHeaderBufInitialSize);
+}
+
+QuicSpdyServerStream::~QuicSpdyServerStream() {
+}
+
+uint32 QuicSpdyServerStream::ProcessData(const char* data, uint32 data_len) {
+  if (data_len > INT_MAX) {
+    LOG(DFATAL) << "Data length too long: " << data_len;
+    return 0;
+  }
+  // Are we still reading the request headers.
+  if (!request_headers_received_) {
+    // Grow the read buffer if necessary.
+    while (read_buf_->RemainingCapacity() < static_cast<int>(data_len)) {
+      read_buf_->SetCapacity(read_buf_->capacity() * 2);
+    }
+    memcpy(read_buf_->data(), data, data_len);
+    read_buf_->set_offset(read_buf_->offset() + data_len);
+    // Try parsing the request headers. This will set request_headers_received_
+    // if successful; if not, it will be tried again with more data.
+    ParseRequestHeaders();
+  } else {
+    body_.append(data, data_len);
+  }
+  return data_len;
+}
+
+void QuicSpdyServerStream::OnFinRead() {
+  ReliableQuicStream::OnFinRead();
+  if (write_side_closed() || fin_buffered()) {
+    return;
+  }
+
+  if (!request_headers_received_) {
+    SendErrorResponse();  // We're not done reading headers.
+    return;
+  }
+
+  SpdyHeaderBlock::const_iterator it = headers_.find("content-length");
+  size_t content_length;
+  if (it != headers_.end() &&
+      (!base::StringToSizeT(it->second, &content_length) ||
+       body_.size() != content_length)) {
+    SendErrorResponse();  // Invalid content length
+    return;
+  }
+
+  SendResponse();
+}
+
+// Try parsing the request headers. If successful, sets
+// request_headers_received_. If not successful, it can just be tried again once
+// there's more data.
+void QuicSpdyServerStream::ParseRequestHeaders() {
+  SpdyFramer framer((kDefaultSpdyMajorVersion));
+  const char* data = read_buf_->StartOfBuffer();
+  size_t read_buf_len = static_cast<size_t>(read_buf_->offset());
+  size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_len, &headers_);
+  if (len == 0) {
+    // Not enough data yet, presumably. (If we still don't succeed by the end of
+    // the stream, then we'll error in OnFinRead().)
+    return;
+  }
+
+  // Headers received and parsed: extract the request URL.
+  request_url_ = GetUrlFromHeaderBlock(headers_,
+                                       kDefaultSpdyMajorVersion,
+                                       false);
+  if (!request_url_.is_valid()) {
+    SendErrorResponse();
+    return;
+  }
+
+  // Add any data past the headers to the request body.
+  size_t delta = read_buf_len - len;
+  if (delta > 0) {
+    body_.append(data + len, delta);
+  }
+
+  request_headers_received_ = true;
+}
+
+void QuicSpdyServerStream::SendResponse() {
+  // Find response in cache. If not found, send error response.
+  const QuicInMemoryCache::Response* response =
+      QuicInMemoryCache::GetInstance()->GetResponse(request_url_);
+  if (response == nullptr) {
+    SendErrorResponse();
+    return;
+  }
+
+  if (response->response_type() == QuicInMemoryCache::CLOSE_CONNECTION) {
+    DVLOG(1) << "Special response: closing connection.";
+    CloseConnection(QUIC_NO_ERROR);
+    return;
+  }
+
+  if (response->response_type() == QuicInMemoryCache::IGNORE_REQUEST) {
+    DVLOG(1) << "Special response: ignoring request.";
+    return;
+  }
+
+  DVLOG(1) << "Sending response for stream " << id();
+  SendHeadersAndBody(response->headers(), response->body());
+}
+
+void QuicSpdyServerStream::SendErrorResponse() {
+  DVLOG(1) << "Sending error response for stream " << id();
+  scoped_refptr<HttpResponseHeaders> headers
+      = new HttpResponseHeaders(string("HTTP/1.1 500 Server Error") + '\0' +
+                                "content-length: 3" + '\0' + '\0');
+  SendHeadersAndBody(*headers.get(), "bad");
+}
+
+void QuicSpdyServerStream::SendHeadersAndBody(
+    const HttpResponseHeaders& response_headers,
+    StringPiece body) {
+  // We only support SPDY and HTTP, and neither handles bidirectional streaming.
+  if (!read_side_closed()) {
+    CloseReadSide();
+  }
+
+  SpdyHeaderBlock header_block;
+  CreateSpdyHeadersFromHttpResponse(response_headers,
+                                    kDefaultSpdyMajorVersion,
+                                    &header_block);
+
+  WriteHeaders(header_block, body.empty(), nullptr);
+
+  if (!body.empty()) {
+    WriteOrBufferData(body, true, nullptr);
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_spdy_server_stream.h b/net/quic/quic_spdy_server_stream.h
new file mode 100644
index 0000000..3cecb6b
--- /dev/null
+++ b/net/quic/quic_spdy_server_stream.h
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SPDY_SERVER_STREAM_H_
+#define NET_QUIC_QUIC_SPDY_SERVER_STREAM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/io_buffer.h"
+#include "net/quic/quic_data_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+class QuicSession;
+
+namespace test {
+class QuicSpdyServerStreamPeer;
+}  // namespace test
+
+// All this does right now is aggregate data, and on fin, send an HTTP
+// response.
+class QuicSpdyServerStream : public QuicDataStream {
+ public:
+  QuicSpdyServerStream(QuicStreamId id, QuicSession* session);
+  virtual ~QuicSpdyServerStream();
+
+  // ReliableQuicStream implementation called by the session when there's
+  // data for us.
+  virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+  virtual void OnFinRead() OVERRIDE;
+
+  void ParseRequestHeaders();
+
+ private:
+  friend class test::QuicSpdyServerStreamPeer;
+
+  // Sends a basic 200 response using SendHeaders for the headers and WriteData
+  // for the body.
+  void SendResponse();
+
+  // Sends a basic 500 response using SendHeaders for the headers and WriteData
+  // for the body
+  void SendErrorResponse();
+
+  void SendHeadersAndBody(const HttpResponseHeaders& response_headers,
+                          base::StringPiece body);
+
+  SpdyHeaderBlock headers_;
+  string body_;
+  GURL request_url_;
+
+  // Buffer into which response header data is read.
+  scoped_refptr<GrowableIOBuffer> read_buf_;
+  bool request_headers_received_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSpdyServerStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SPDY_SERVER_STREAM_H_
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
new file mode 100644
index 0000000..ccbca64
--- /dev/null
+++ b/net/quic/quic_stream_factory.cc
@@ -0,0 +1,1034 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_stream_factory.h"
+
+#include <set>
+
+#include "base/cpu.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/http/http_server_properties.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/crypto/channel_id_chromium.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/crypto/quic_server_info.h"
+#include "net/quic/port_suggester.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_default_packet_writer.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_id.h"
+#include "net/socket/client_socket_factory.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+enum CreateSessionFailure {
+  CREATION_ERROR_CONNECTING_SOCKET,
+  CREATION_ERROR_SETTING_RECEIVE_BUFFER,
+  CREATION_ERROR_SETTING_SEND_BUFFER,
+  CREATION_ERROR_MAX
+};
+
+// When a connection is idle for 30 seconds it will be closed.
+const int kIdleConnectionTimeoutSeconds = 30;
+
+// The initial receive window size for both streams and sessions.
+const int32 kInitialReceiveWindowSize = 10 * 1024 * 1024;  // 10MB
+
+// The suggested initial congestion windows for a server to use.
+// TODO: This should be tested and optimized, and even better, suggest a window
+// that corresponds to historical bandwidth and min-RTT.
+// Larger initial congestion windows can, if we don't overshoot, reduce latency
+// by avoiding the RTT needed for slow start to double (and re-double) from a
+// default of 10.
+// We match SPDY's use of 32 when secure (since we'd compete with SPDY).
+const int32 kServerSecureInitialCongestionWindow = 32;
+// Be conservative, and just use double a typical TCP  ICWND for HTTP.
+const int32 kServerInecureInitialCongestionWindow = 20;
+
+const char kDummyHostname[] = "quic.global.props";
+const uint16 kDummyPort = 0;
+
+void HistogramCreateSessionFailure(enum CreateSessionFailure error) {
+  UMA_HISTOGRAM_ENUMERATION("Net.QuicSession.CreationError", error,
+                            CREATION_ERROR_MAX);
+}
+
+bool IsEcdsaSupported() {
+#if defined(OS_WIN)
+  if (base::win::GetVersion() < base::win::VERSION_VISTA)
+    return false;
+#endif
+
+  return true;
+}
+
+QuicConfig InitializeQuicConfig(const QuicTagVector& connection_options) {
+  QuicConfig config;
+  config.SetDefaults();
+  config.SetIdleConnectionStateLifetime(
+      QuicTime::Delta::FromSeconds(kIdleConnectionTimeoutSeconds),
+      QuicTime::Delta::FromSeconds(kIdleConnectionTimeoutSeconds));
+  config.SetConnectionOptionsToSend(connection_options);
+  return config;
+}
+
+class DefaultPacketWriterFactory : public QuicConnection::PacketWriterFactory {
+ public:
+  explicit DefaultPacketWriterFactory(DatagramClientSocket* socket)
+      : socket_(socket) {}
+  virtual ~DefaultPacketWriterFactory() {}
+
+  virtual QuicPacketWriter* Create(QuicConnection* connection) const OVERRIDE;
+
+ private:
+  DatagramClientSocket* socket_;
+};
+
+QuicPacketWriter* DefaultPacketWriterFactory::Create(
+    QuicConnection* connection) const {
+  scoped_ptr<QuicDefaultPacketWriter> writer(
+      new QuicDefaultPacketWriter(socket_));
+  writer->SetConnection(connection);
+  return writer.release();
+}
+
+}  // namespace
+
+QuicStreamFactory::IpAliasKey::IpAliasKey() {}
+
+QuicStreamFactory::IpAliasKey::IpAliasKey(IPEndPoint ip_endpoint,
+                                          bool is_https)
+    : ip_endpoint(ip_endpoint),
+      is_https(is_https) {}
+
+QuicStreamFactory::IpAliasKey::~IpAliasKey() {}
+
+bool QuicStreamFactory::IpAliasKey::operator<(
+    const QuicStreamFactory::IpAliasKey& other) const {
+  if (!(ip_endpoint == other.ip_endpoint)) {
+    return ip_endpoint < other.ip_endpoint;
+  }
+  return is_https < other.is_https;
+}
+
+bool QuicStreamFactory::IpAliasKey::operator==(
+    const QuicStreamFactory::IpAliasKey& other) const {
+  return is_https == other.is_https &&
+      ip_endpoint == other.ip_endpoint;
+};
+
+// Responsible for creating a new QUIC session to the specified server, and
+// for notifying any associated requests when complete.
+class QuicStreamFactory::Job {
+ public:
+  Job(QuicStreamFactory* factory,
+      HostResolver* host_resolver,
+      const HostPortPair& host_port_pair,
+      bool is_https,
+      bool was_alternate_protocol_recently_broken,
+      PrivacyMode privacy_mode,
+      base::StringPiece method,
+      QuicServerInfo* server_info,
+      const BoundNetLog& net_log);
+
+  // Creates a new job to handle the resumption of for connecting an
+  // existing session.
+  Job(QuicStreamFactory* factory,
+      HostResolver* host_resolver,
+      QuicClientSession* session,
+      QuicServerId server_id);
+
+  ~Job();
+
+  int Run(const CompletionCallback& callback);
+
+  int DoLoop(int rv);
+  int DoResolveHost();
+  int DoResolveHostComplete(int rv);
+  int DoLoadServerInfo();
+  int DoLoadServerInfoComplete(int rv);
+  int DoConnect();
+  int DoResumeConnect();
+  int DoConnectComplete(int rv);
+
+  void OnIOComplete(int rv);
+
+  CompletionCallback callback() {
+    return callback_;
+  }
+
+  const QuicServerId server_id() const {
+    return server_id_;
+  }
+
+ private:
+  enum IoState {
+    STATE_NONE,
+    STATE_RESOLVE_HOST,
+    STATE_RESOLVE_HOST_COMPLETE,
+    STATE_LOAD_SERVER_INFO,
+    STATE_LOAD_SERVER_INFO_COMPLETE,
+    STATE_CONNECT,
+    STATE_RESUME_CONNECT,
+    STATE_CONNECT_COMPLETE,
+  };
+  IoState io_state_;
+
+  QuicStreamFactory* factory_;
+  SingleRequestHostResolver host_resolver_;
+  QuicServerId server_id_;
+  bool is_post_;
+  bool was_alternate_protocol_recently_broken_;
+  scoped_ptr<QuicServerInfo> server_info_;
+  const BoundNetLog net_log_;
+  QuicClientSession* session_;
+  CompletionCallback callback_;
+  AddressList address_list_;
+  base::TimeTicks disk_cache_load_start_time_;
+  base::TimeTicks dns_resolution_start_time_;
+  base::WeakPtrFactory<Job> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+QuicStreamFactory::Job::Job(QuicStreamFactory* factory,
+                            HostResolver* host_resolver,
+                            const HostPortPair& host_port_pair,
+                            bool is_https,
+                            bool was_alternate_protocol_recently_broken,
+                            PrivacyMode privacy_mode,
+                            base::StringPiece method,
+                            QuicServerInfo* server_info,
+                            const BoundNetLog& net_log)
+    : io_state_(STATE_RESOLVE_HOST),
+      factory_(factory),
+      host_resolver_(host_resolver),
+      server_id_(host_port_pair, is_https, privacy_mode),
+      is_post_(method == "POST"),
+      was_alternate_protocol_recently_broken_(
+          was_alternate_protocol_recently_broken),
+      server_info_(server_info),
+      net_log_(net_log),
+      session_(nullptr),
+      weak_factory_(this) {}
+
+QuicStreamFactory::Job::Job(QuicStreamFactory* factory,
+                            HostResolver* host_resolver,
+                            QuicClientSession* session,
+                            QuicServerId server_id)
+    : io_state_(STATE_RESUME_CONNECT),
+      factory_(factory),
+      host_resolver_(host_resolver),  // unused
+      server_id_(server_id),
+      is_post_(false),  // unused
+      was_alternate_protocol_recently_broken_(false),  // unused
+      net_log_(session->net_log()),  // unused
+      session_(session),
+      weak_factory_(this) {}
+
+QuicStreamFactory::Job::~Job() {
+}
+
+int QuicStreamFactory::Job::Run(const CompletionCallback& callback) {
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING)
+    callback_ = callback;
+
+  return rv > 0 ? OK : rv;
+}
+
+int QuicStreamFactory::Job::DoLoop(int rv) {
+  do {
+    IoState state = io_state_;
+    io_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_RESOLVE_HOST:
+        CHECK_EQ(OK, rv);
+        rv = DoResolveHost();
+        break;
+      case STATE_RESOLVE_HOST_COMPLETE:
+        rv = DoResolveHostComplete(rv);
+        break;
+      case STATE_LOAD_SERVER_INFO:
+        CHECK_EQ(OK, rv);
+        rv = DoLoadServerInfo();
+        break;
+      case STATE_LOAD_SERVER_INFO_COMPLETE:
+        rv = DoLoadServerInfoComplete(rv);
+        break;
+      case STATE_CONNECT:
+        CHECK_EQ(OK, rv);
+        rv = DoConnect();
+        break;
+      case STATE_RESUME_CONNECT:
+        CHECK_EQ(OK, rv);
+        rv = DoResumeConnect();
+        break;
+      case STATE_CONNECT_COMPLETE:
+        rv = DoConnectComplete(rv);
+        break;
+      default:
+        NOTREACHED() << "io_state_: " << io_state_;
+        break;
+    }
+  } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
+  return rv;
+}
+
+void QuicStreamFactory::Job::OnIOComplete(int rv) {
+  rv = DoLoop(rv);
+
+  if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+    callback_.Run(rv);
+  }
+}
+
+int QuicStreamFactory::Job::DoResolveHost() {
+  // Start loading the data now, and wait for it after we resolve the host.
+  if (server_info_) {
+    disk_cache_load_start_time_ = base::TimeTicks::Now();
+    server_info_->Start();
+  }
+
+  io_state_ = STATE_RESOLVE_HOST_COMPLETE;
+  dns_resolution_start_time_ = base::TimeTicks::Now();
+  return host_resolver_.Resolve(
+      HostResolver::RequestInfo(server_id_.host_port_pair()),
+      DEFAULT_PRIORITY,
+      &address_list_,
+      base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+                 weak_factory_.GetWeakPtr()),
+      net_log_);
+}
+
+int QuicStreamFactory::Job::DoResolveHostComplete(int rv) {
+  UMA_HISTOGRAM_TIMES("Net.QuicSession.HostResolutionTime",
+                      base::TimeTicks::Now() - dns_resolution_start_time_);
+  if (rv != OK)
+    return rv;
+
+  DCHECK(!factory_->HasActiveSession(server_id_));
+
+  // Inform the factory of this resolution, which will set up
+  // a session alias, if possible.
+  if (factory_->OnResolution(server_id_, address_list_)) {
+    return OK;
+  }
+
+  io_state_ = STATE_LOAD_SERVER_INFO;
+  return OK;
+}
+
+int QuicStreamFactory::Job::DoLoadServerInfo() {
+  io_state_ = STATE_LOAD_SERVER_INFO_COMPLETE;
+
+  if (!server_info_)
+    return OK;
+
+  return server_info_->WaitForDataReady(
+      base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+                 weak_factory_.GetWeakPtr()));
+}
+
+int QuicStreamFactory::Job::DoLoadServerInfoComplete(int rv) {
+  if (server_info_) {
+    UMA_HISTOGRAM_TIMES("Net.QuicServerInfo.DiskCacheReadTime",
+                        base::TimeTicks::Now() - disk_cache_load_start_time_);
+  }
+
+  if (rv != OK) {
+    server_info_.reset();
+  }
+
+  io_state_ = STATE_CONNECT;
+  return OK;
+}
+
+int QuicStreamFactory::Job::DoConnect() {
+  io_state_ = STATE_CONNECT_COMPLETE;
+
+  int rv = factory_->CreateSession(server_id_, server_info_.Pass(),
+                                   address_list_, net_log_, &session_);
+  if (rv != OK) {
+    DCHECK(rv != ERR_IO_PENDING);
+    DCHECK(!session_);
+    return rv;
+  }
+
+  if (!session_->connection()->connected()) {
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  session_->StartReading();
+  if (!session_->connection()->connected()) {
+    return ERR_QUIC_PROTOCOL_ERROR;
+  }
+  bool require_confirmation =
+      factory_->require_confirmation() || is_post_ ||
+      was_alternate_protocol_recently_broken_;
+  rv = session_->CryptoConnect(
+      require_confirmation,
+      base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+                 base::Unretained(this)));
+  return rv;
+}
+
+int QuicStreamFactory::Job::DoResumeConnect() {
+  io_state_ = STATE_CONNECT_COMPLETE;
+
+  int rv = session_->ResumeCryptoConnect(
+      base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+                 base::Unretained(this)));
+
+  return rv;
+}
+
+int QuicStreamFactory::Job::DoConnectComplete(int rv) {
+  if (rv != OK)
+    return rv;
+
+  DCHECK(!factory_->HasActiveSession(server_id_));
+  // There may well now be an active session for this IP.  If so, use the
+  // existing session instead.
+  AddressList address(session_->connection()->peer_address());
+  if (factory_->OnResolution(server_id_, address)) {
+    session_->connection()->SendConnectionClose(QUIC_CONNECTION_IP_POOLED);
+    session_ = nullptr;
+    return OK;
+  }
+
+  factory_->ActivateSession(server_id_, session_);
+
+  return OK;
+}
+
+QuicStreamRequest::QuicStreamRequest(QuicStreamFactory* factory)
+    : factory_(factory) {}
+
+QuicStreamRequest::~QuicStreamRequest() {
+  if (factory_ && !callback_.is_null())
+    factory_->CancelRequest(this);
+}
+
+int QuicStreamRequest::Request(const HostPortPair& host_port_pair,
+                               bool is_https,
+                               PrivacyMode privacy_mode,
+                               base::StringPiece method,
+                               const BoundNetLog& net_log,
+                               const CompletionCallback& callback) {
+  DCHECK(!stream_);
+  DCHECK(callback_.is_null());
+  DCHECK(factory_);
+  int rv = factory_->Create(host_port_pair, is_https, privacy_mode, method,
+                            net_log, this);
+  if (rv == ERR_IO_PENDING) {
+    host_port_pair_ = host_port_pair;
+    is_https_ = is_https;
+    net_log_ = net_log;
+    callback_ = callback;
+  } else {
+    factory_ = nullptr;
+  }
+  if (rv == OK)
+    DCHECK(stream_);
+  return rv;
+}
+
+void QuicStreamRequest::set_stream(scoped_ptr<QuicHttpStream> stream) {
+  DCHECK(stream);
+  stream_ = stream.Pass();
+}
+
+void QuicStreamRequest::OnRequestComplete(int rv) {
+  factory_ = nullptr;
+  callback_.Run(rv);
+}
+
+scoped_ptr<QuicHttpStream> QuicStreamRequest::ReleaseStream() {
+  DCHECK(stream_);
+  return stream_.Pass();
+}
+
+QuicStreamFactory::QuicStreamFactory(
+    HostResolver* host_resolver,
+    ClientSocketFactory* client_socket_factory,
+    base::WeakPtr<HttpServerProperties> http_server_properties,
+    CertVerifier* cert_verifier,
+    ChannelIDService* channel_id_service,
+    TransportSecurityState* transport_security_state,
+    QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory,
+    QuicRandom* random_generator,
+    QuicClock* clock,
+    size_t max_packet_length,
+    const std::string& user_agent_id,
+    const QuicVersionVector& supported_versions,
+    bool enable_port_selection,
+    bool always_require_handshake_confirmation,
+    bool disable_connection_pooling,
+    const QuicTagVector& connection_options)
+    : require_confirmation_(true),
+      host_resolver_(host_resolver),
+      client_socket_factory_(client_socket_factory),
+      http_server_properties_(http_server_properties),
+      transport_security_state_(transport_security_state),
+      quic_server_info_factory_(nullptr),
+      quic_crypto_client_stream_factory_(quic_crypto_client_stream_factory),
+      random_generator_(random_generator),
+      clock_(clock),
+      max_packet_length_(max_packet_length),
+      config_(InitializeQuicConfig(connection_options)),
+      supported_versions_(supported_versions),
+      enable_port_selection_(enable_port_selection),
+      always_require_handshake_confirmation_(
+          always_require_handshake_confirmation),
+      disable_connection_pooling_(disable_connection_pooling),
+      port_seed_(random_generator_->RandUint64()),
+      check_persisted_supports_quic_(true),
+      weak_factory_(this) {
+  DCHECK(transport_security_state_);
+  crypto_config_.SetDefaults();
+  crypto_config_.set_user_agent_id(user_agent_id);
+  crypto_config_.AddCanonicalSuffix(".c.youtube.com");
+  crypto_config_.AddCanonicalSuffix(".googlevideo.com");
+  crypto_config_.SetProofVerifier(
+      new ProofVerifierChromium(cert_verifier, transport_security_state));
+  crypto_config_.SetChannelIDSource(
+      new ChannelIDSourceChromium(channel_id_service));
+  base::CPU cpu;
+  if (cpu.has_aesni() && cpu.has_avx())
+    crypto_config_.PreferAesGcm();
+  if (!IsEcdsaSupported())
+    crypto_config_.DisableEcdsa();
+}
+
+QuicStreamFactory::~QuicStreamFactory() {
+  CloseAllSessions(ERR_ABORTED);
+  while (!all_sessions_.empty()) {
+    delete all_sessions_.begin()->first;
+    all_sessions_.erase(all_sessions_.begin());
+  }
+  STLDeleteValues(&active_jobs_);
+}
+
+void QuicStreamFactory::set_require_confirmation(bool require_confirmation) {
+  require_confirmation_ = require_confirmation;
+  if (http_server_properties_ && (!(local_address_ == IPEndPoint()))) {
+    // TODO(rtenneti): Delete host_port_pair and persist data in globals.
+    HostPortPair host_port_pair(kDummyHostname, kDummyPort);
+    http_server_properties_->SetSupportsQuic(
+        host_port_pair, !require_confirmation,
+        local_address_.ToStringWithoutPort());
+  }
+}
+
+int QuicStreamFactory::Create(const HostPortPair& host_port_pair,
+                              bool is_https,
+                              PrivacyMode privacy_mode,
+                              base::StringPiece method,
+                              const BoundNetLog& net_log,
+                              QuicStreamRequest* request) {
+  QuicServerId server_id(host_port_pair, is_https, privacy_mode);
+  if (HasActiveSession(server_id)) {
+    request->set_stream(CreateIfSessionExists(server_id, net_log));
+    return OK;
+  }
+
+  if (HasActiveJob(server_id)) {
+    Job* job = active_jobs_[server_id];
+    active_requests_[request] = job;
+    job_requests_map_[job].insert(request);
+    return ERR_IO_PENDING;
+  }
+
+  QuicServerInfo* quic_server_info = nullptr;
+  if (quic_server_info_factory_) {
+    QuicCryptoClientConfig::CachedState* cached =
+        crypto_config_.LookupOrCreate(server_id);
+    DCHECK(cached);
+    if (cached->IsEmpty()) {
+      quic_server_info = quic_server_info_factory_->GetForServer(server_id);
+    }
+  }
+  bool was_alternate_protocol_recently_broken =
+      http_server_properties_ &&
+      http_server_properties_->WasAlternateProtocolRecentlyBroken(
+          server_id.host_port_pair());
+  scoped_ptr<Job> job(new Job(this, host_resolver_, host_port_pair, is_https,
+                              was_alternate_protocol_recently_broken,
+                              privacy_mode, method, quic_server_info, net_log));
+  int rv = job->Run(base::Bind(&QuicStreamFactory::OnJobComplete,
+                               base::Unretained(this), job.get()));
+
+  if (rv == ERR_IO_PENDING) {
+    active_requests_[request] = job.get();
+    job_requests_map_[job.get()].insert(request);
+    active_jobs_[server_id] = job.release();
+  }
+  if (rv == OK) {
+    DCHECK(HasActiveSession(server_id));
+    request->set_stream(CreateIfSessionExists(server_id, net_log));
+  }
+  return rv;
+}
+
+bool QuicStreamFactory::OnResolution(
+    const QuicServerId& server_id,
+    const AddressList& address_list) {
+  DCHECK(!HasActiveSession(server_id));
+  if (disable_connection_pooling_) {
+    return false;
+  }
+  for (size_t i = 0; i < address_list.size(); ++i) {
+    const IPEndPoint& address = address_list[i];
+    const IpAliasKey ip_alias_key(address, server_id.is_https());
+    if (!ContainsKey(ip_aliases_, ip_alias_key))
+      continue;
+
+    const SessionSet& sessions = ip_aliases_[ip_alias_key];
+    for (SessionSet::const_iterator i = sessions.begin();
+         i != sessions.end(); ++i) {
+      QuicClientSession* session = *i;
+      if (!session->CanPool(server_id.host()))
+        continue;
+      active_sessions_[server_id] = session;
+      session_aliases_[session].insert(server_id);
+      return true;
+    }
+  }
+  return false;
+}
+
+void QuicStreamFactory::OnJobComplete(Job* job, int rv) {
+  if (rv == OK) {
+    if (!always_require_handshake_confirmation_)
+      set_require_confirmation(false);
+
+    // Create all the streams, but do not notify them yet.
+    for (RequestSet::iterator it = job_requests_map_[job].begin();
+         it != job_requests_map_[job].end() ; ++it) {
+      DCHECK(HasActiveSession(job->server_id()));
+      (*it)->set_stream(CreateIfSessionExists(job->server_id(),
+                                              (*it)->net_log()));
+    }
+  }
+  while (!job_requests_map_[job].empty()) {
+    RequestSet::iterator it = job_requests_map_[job].begin();
+    QuicStreamRequest* request = *it;
+    job_requests_map_[job].erase(it);
+    active_requests_.erase(request);
+    // Even though we're invoking callbacks here, we don't need to worry
+    // about |this| being deleted, because the factory is owned by the
+    // profile which can not be deleted via callbacks.
+    request->OnRequestComplete(rv);
+  }
+  active_jobs_.erase(job->server_id());
+  job_requests_map_.erase(job);
+  delete job;
+  return;
+}
+
+// Returns a newly created QuicHttpStream owned by the caller, if a
+// matching session already exists.  Returns nullptr otherwise.
+scoped_ptr<QuicHttpStream> QuicStreamFactory::CreateIfSessionExists(
+    const QuicServerId& server_id,
+    const BoundNetLog& net_log) {
+  if (!HasActiveSession(server_id)) {
+    DVLOG(1) << "No active session";
+    return scoped_ptr<QuicHttpStream>();
+  }
+
+  QuicClientSession* session = active_sessions_[server_id];
+  DCHECK(session);
+  return scoped_ptr<QuicHttpStream>(
+      new QuicHttpStream(session->GetWeakPtr()));
+}
+
+void QuicStreamFactory::OnIdleSession(QuicClientSession* session) {
+}
+
+void QuicStreamFactory::OnSessionGoingAway(QuicClientSession* session) {
+  const AliasSet& aliases = session_aliases_[session];
+  for (AliasSet::const_iterator it = aliases.begin(); it != aliases.end();
+       ++it) {
+    DCHECK(active_sessions_.count(*it));
+    DCHECK_EQ(session, active_sessions_[*it]);
+    // Track sessions which have recently gone away so that we can disable
+    // port suggestions.
+    if (session->goaway_received()) {
+      gone_away_aliases_.insert(*it);
+    }
+
+    active_sessions_.erase(*it);
+    ProcessGoingAwaySession(session, *it, true);
+  }
+  ProcessGoingAwaySession(session, all_sessions_[session], false);
+  if (!aliases.empty()) {
+    const IpAliasKey ip_alias_key(session->connection()->peer_address(),
+                                  aliases.begin()->is_https());
+    ip_aliases_[ip_alias_key].erase(session);
+    if (ip_aliases_[ip_alias_key].empty()) {
+      ip_aliases_.erase(ip_alias_key);
+    }
+  }
+  session_aliases_.erase(session);
+}
+
+void QuicStreamFactory::OnSessionClosed(QuicClientSession* session) {
+  DCHECK_EQ(0u, session->GetNumOpenStreams());
+  OnSessionGoingAway(session);
+  delete session;
+  all_sessions_.erase(session);
+}
+
+void QuicStreamFactory::OnSessionConnectTimeout(
+    QuicClientSession* session) {
+  const AliasSet& aliases = session_aliases_[session];
+  for (AliasSet::const_iterator it = aliases.begin(); it != aliases.end();
+       ++it) {
+    DCHECK(active_sessions_.count(*it));
+    DCHECK_EQ(session, active_sessions_[*it]);
+    active_sessions_.erase(*it);
+  }
+
+  if (aliases.empty()) {
+    return;
+  }
+
+  const IpAliasKey ip_alias_key(session->connection()->peer_address(),
+                                aliases.begin()->is_https());
+  ip_aliases_[ip_alias_key].erase(session);
+  if (ip_aliases_[ip_alias_key].empty()) {
+    ip_aliases_.erase(ip_alias_key);
+  }
+  QuicServerId server_id = *aliases.begin();
+  session_aliases_.erase(session);
+  Job* job = new Job(this, host_resolver_, session, server_id);
+  active_jobs_[server_id] = job;
+  int rv = job->Run(base::Bind(&QuicStreamFactory::OnJobComplete,
+                               base::Unretained(this), job));
+  DCHECK_EQ(ERR_IO_PENDING, rv);
+}
+
+void QuicStreamFactory::CancelRequest(QuicStreamRequest* request) {
+  DCHECK(ContainsKey(active_requests_, request));
+  Job* job = active_requests_[request];
+  job_requests_map_[job].erase(request);
+  active_requests_.erase(request);
+}
+
+void QuicStreamFactory::CloseAllSessions(int error) {
+  while (!active_sessions_.empty()) {
+    size_t initial_size = active_sessions_.size();
+    active_sessions_.begin()->second->CloseSessionOnError(error);
+    DCHECK_NE(initial_size, active_sessions_.size());
+  }
+  while (!all_sessions_.empty()) {
+    size_t initial_size = all_sessions_.size();
+    all_sessions_.begin()->first->CloseSessionOnError(error);
+    DCHECK_NE(initial_size, all_sessions_.size());
+  }
+  DCHECK(all_sessions_.empty());
+}
+
+base::Value* QuicStreamFactory::QuicStreamFactoryInfoToValue() const {
+  base::ListValue* list = new base::ListValue();
+
+  for (SessionMap::const_iterator it = active_sessions_.begin();
+       it != active_sessions_.end(); ++it) {
+    const QuicServerId& server_id = it->first;
+    QuicClientSession* session = it->second;
+    const AliasSet& aliases = session_aliases_.find(session)->second;
+    // Only add a session to the list once.
+    if (server_id == *aliases.begin()) {
+      std::set<HostPortPair> hosts;
+      for (AliasSet::const_iterator alias_it = aliases.begin();
+           alias_it != aliases.end(); ++alias_it) {
+        hosts.insert(alias_it->host_port_pair());
+      }
+      list->Append(session->GetInfoAsValue(hosts));
+    }
+  }
+  return list;
+}
+
+void QuicStreamFactory::ClearCachedStatesInCryptoConfig() {
+  crypto_config_.ClearCachedStates();
+}
+
+void QuicStreamFactory::OnIPAddressChanged() {
+  CloseAllSessions(ERR_NETWORK_CHANGED);
+  set_require_confirmation(true);
+}
+
+void QuicStreamFactory::OnCertAdded(const X509Certificate* cert) {
+  CloseAllSessions(ERR_CERT_DATABASE_CHANGED);
+}
+
+void QuicStreamFactory::OnCACertChanged(const X509Certificate* cert) {
+  // We should flush the sessions if we removed trust from a
+  // cert, because a previously trusted server may have become
+  // untrusted.
+  //
+  // We should not flush the sessions if we added trust to a cert.
+  //
+  // Since the OnCACertChanged method doesn't tell us what
+  // kind of change it is, we have to flush the socket
+  // pools to be safe.
+  CloseAllSessions(ERR_CERT_DATABASE_CHANGED);
+}
+
+bool QuicStreamFactory::HasActiveSession(
+    const QuicServerId& server_id) const {
+  return ContainsKey(active_sessions_, server_id);
+}
+
+int QuicStreamFactory::CreateSession(
+    const QuicServerId& server_id,
+    scoped_ptr<QuicServerInfo> server_info,
+    const AddressList& address_list,
+    const BoundNetLog& net_log,
+    QuicClientSession** session) {
+  bool enable_port_selection = enable_port_selection_;
+  if (enable_port_selection &&
+      ContainsKey(gone_away_aliases_, server_id)) {
+    // Disable port selection when the server is going away.
+    // There is no point in trying to return to the same server, if
+    // that server is no longer handling requests.
+    enable_port_selection = false;
+    gone_away_aliases_.erase(server_id);
+  }
+
+  QuicConnectionId connection_id = random_generator_->RandUint64();
+  IPEndPoint addr = *address_list.begin();
+  scoped_refptr<PortSuggester> port_suggester =
+      new PortSuggester(server_id.host_port_pair(), port_seed_);
+  DatagramSocket::BindType bind_type = enable_port_selection ?
+      DatagramSocket::RANDOM_BIND :  // Use our callback.
+      DatagramSocket::DEFAULT_BIND;  // Use OS to randomize.
+  scoped_ptr<DatagramClientSocket> socket(
+      client_socket_factory_->CreateDatagramClientSocket(
+          bind_type,
+          base::Bind(&PortSuggester::SuggestPort, port_suggester),
+          net_log.net_log(), net_log.source()));
+  int rv = socket->Connect(addr);
+  if (rv != OK) {
+    HistogramCreateSessionFailure(CREATION_ERROR_CONNECTING_SOCKET);
+    return rv;
+  }
+  UMA_HISTOGRAM_COUNTS("Net.QuicEphemeralPortsSuggested",
+                       port_suggester->call_count());
+  if (enable_port_selection) {
+    DCHECK_LE(1u, port_suggester->call_count());
+  } else {
+    DCHECK_EQ(0u, port_suggester->call_count());
+  }
+
+  // We should adaptively set this buffer size, but for now, we'll use a size
+  // that is more than large enough for a full receive window, and yet
+  // does not consume "too much" memory.  If we see bursty packet loss, we may
+  // revisit this setting and test for its impact.
+  const int32 kSocketBufferSize(TcpReceiver::kReceiveWindowTCP);
+  rv = socket->SetReceiveBufferSize(kSocketBufferSize);
+  if (rv != OK) {
+    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_RECEIVE_BUFFER);
+    return rv;
+  }
+  // Set a buffer large enough to contain the initial CWND's worth of packet
+  // to work around the problem with CHLO packets being sent out with the
+  // wrong encryption level, when the send buffer is full.
+  rv = socket->SetSendBufferSize(kMaxPacketSize * 20);
+  if (rv != OK) {
+    HistogramCreateSessionFailure(CREATION_ERROR_SETTING_SEND_BUFFER);
+    return rv;
+  }
+
+  socket->GetLocalAddress(&local_address_);
+  if (check_persisted_supports_quic_ && http_server_properties_) {
+    check_persisted_supports_quic_ = false;
+    // TODO(rtenneti): Delete host_port_pair and persist data in globals.
+    HostPortPair host_port_pair(kDummyHostname, kDummyPort);
+    SupportsQuic supports_quic(true, local_address_.ToStringWithoutPort());
+    if (http_server_properties_->GetSupportsQuic(
+            host_port_pair).Equals(supports_quic)) {
+      require_confirmation_ = false;
+    }
+  }
+
+  DefaultPacketWriterFactory packet_writer_factory(socket.get());
+
+  if (!helper_.get()) {
+    helper_.reset(new QuicConnectionHelper(
+        base::MessageLoop::current()->message_loop_proxy().get(),
+        clock_.get(), random_generator_));
+  }
+
+  QuicConnection* connection = new QuicConnection(connection_id,
+                                                  addr,
+                                                  helper_.get(),
+                                                  packet_writer_factory,
+                                                  true  /* owns_writer */,
+                                                  false  /* is_server */,
+                                                  supported_versions_);
+  connection->set_max_packet_length(max_packet_length_);
+
+  InitializeCachedStateInCryptoConfig(server_id, server_info);
+
+  QuicConfig config = config_;
+  config.SetInitialCongestionWindowToSend(
+      server_id.is_https() ? kServerSecureInitialCongestionWindow
+                           : kServerInecureInitialCongestionWindow);
+  config.SetInitialFlowControlWindowToSend(kInitialReceiveWindowSize);
+  config.SetInitialStreamFlowControlWindowToSend(kInitialReceiveWindowSize);
+  config.SetInitialSessionFlowControlWindowToSend(kInitialReceiveWindowSize);
+  if (http_server_properties_) {
+    const HttpServerProperties::NetworkStats* stats =
+        http_server_properties_->GetServerNetworkStats(
+            server_id.host_port_pair());
+    if (stats != nullptr) {
+      config.SetInitialRoundTripTimeUsToSend(stats->srtt.InMicroseconds());
+    }
+  }
+
+  *session = new QuicClientSession(
+      connection, socket.Pass(), this, transport_security_state_,
+      server_info.Pass(), config,
+      base::MessageLoop::current()->message_loop_proxy().get(),
+      net_log.net_log());
+  all_sessions_[*session] = server_id;  // owning pointer
+  (*session)->InitializeSession(server_id,  &crypto_config_,
+                                quic_crypto_client_stream_factory_);
+  bool closed_during_initialize =
+      !ContainsKey(all_sessions_, *session) ||
+      !(*session)->connection()->connected();
+  UMA_HISTOGRAM_BOOLEAN("Net.QuicSession.ClosedDuringInitializeSession",
+                        closed_during_initialize);
+  if (closed_during_initialize) {
+    DLOG(DFATAL) << "Session closed during initialize";
+    *session = nullptr;
+    return ERR_CONNECTION_CLOSED;
+  }
+  return OK;
+}
+
+bool QuicStreamFactory::HasActiveJob(const QuicServerId& key) const {
+  return ContainsKey(active_jobs_, key);
+}
+
+void QuicStreamFactory::ActivateSession(
+    const QuicServerId& server_id,
+    QuicClientSession* session) {
+  DCHECK(!HasActiveSession(server_id));
+  UMA_HISTOGRAM_COUNTS("Net.QuicActiveSessions", active_sessions_.size());
+  active_sessions_[server_id] = session;
+  session_aliases_[session].insert(server_id);
+  const IpAliasKey ip_alias_key(session->connection()->peer_address(),
+                                server_id.is_https());
+  DCHECK(!ContainsKey(ip_aliases_[ip_alias_key], session));
+  ip_aliases_[ip_alias_key].insert(session);
+}
+
+void QuicStreamFactory::InitializeCachedStateInCryptoConfig(
+    const QuicServerId& server_id,
+    const scoped_ptr<QuicServerInfo>& server_info) {
+  if (!server_info)
+    return;
+
+  QuicCryptoClientConfig::CachedState* cached =
+      crypto_config_.LookupOrCreate(server_id);
+  if (!cached->IsEmpty())
+    return;
+
+  if (!cached->Initialize(server_info->state().server_config,
+                          server_info->state().source_address_token,
+                          server_info->state().certs,
+                          server_info->state().server_config_sig,
+                          clock_->WallNow()))
+    return;
+
+  if (!server_id.is_https()) {
+    // Don't check the certificates for insecure QUIC.
+    cached->SetProofValid();
+  }
+}
+
+void QuicStreamFactory::ProcessGoingAwaySession(
+    QuicClientSession* session,
+    const QuicServerId& server_id,
+    bool session_was_active) {
+  if (!http_server_properties_)
+    return;
+
+  const QuicConnectionStats& stats = session->connection()->GetStats();
+  if (session->IsCryptoHandshakeConfirmed()) {
+    HttpServerProperties::NetworkStats network_stats;
+    network_stats.srtt = base::TimeDelta::FromMicroseconds(stats.srtt_us);
+    network_stats.bandwidth_estimate = stats.estimated_bandwidth;
+    http_server_properties_->SetServerNetworkStats(server_id.host_port_pair(),
+                                                   network_stats);
+    return;
+  }
+
+  UMA_HISTOGRAM_COUNTS("Net.QuicHandshakeNotConfirmedNumPacketsReceived",
+                       stats.packets_received);
+
+  if (!session_was_active)
+    return;
+
+  const HostPortPair& server = server_id.host_port_pair();
+  // Don't try to change the alternate-protocol state, if the
+  // alternate-protocol state is unknown.
+  if (!http_server_properties_->HasAlternateProtocol(server))
+    return;
+
+  // TODO(rch):  In the special case where the session has received no
+  // packets from the peer, we should consider blacklisting this
+  // differently so that we still race TCP but we don't consider the
+  // session connected until the handshake has been confirmed.
+  HistogramBrokenAlternateProtocolLocation(
+      BROKEN_ALTERNATE_PROTOCOL_LOCATION_QUIC_STREAM_FACTORY);
+  AlternateProtocolInfo alternate =
+      http_server_properties_->GetAlternateProtocol(server);
+  DCHECK_EQ(QUIC, alternate.protocol);
+
+  // Since the session was active, there's no longer an
+  // HttpStreamFactoryImpl::Job running which can mark it broken, unless the
+  // TCP job also fails. So to avoid not using QUIC when we otherwise could,
+  // we mark it as broken, and then immediately re-enable it. This leaves
+  // QUIC as "recently broken" which means that 0-RTT will be disabled but
+  // we'll still race.
+  http_server_properties_->SetBrokenAlternateProtocol(server);
+  http_server_properties_->ClearAlternateProtocol(server);
+  http_server_properties_->SetAlternateProtocol(
+      server, alternate.port, alternate.protocol, 1);
+  DCHECK_EQ(QUIC,
+            http_server_properties_->GetAlternateProtocol(server).protocol);
+  DCHECK(http_server_properties_->WasAlternateProtocolRecentlyBroken(
+      server));
+}
+
+}  // namespace net
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
new file mode 100644
index 0000000..f4aa77e
--- /dev/null
+++ b/net/quic/quic_stream_factory.h
@@ -0,0 +1,304 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_STREAM_FACTORY_H_
+#define NET_QUIC_QUIC_STREAM_FACTORY_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/cert/cert_database.h"
+#include "net/proxy/proxy_server.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CertVerifier;
+class ChannelIDService;
+class ClientSocketFactory;
+class HostResolver;
+class HttpServerProperties;
+class QuicClock;
+class QuicClientSession;
+class QuicConnectionHelper;
+class QuicCryptoClientStreamFactory;
+class QuicRandom;
+class QuicServerInfoFactory;
+class QuicServerId;
+class QuicStreamFactory;
+class TransportSecurityState;
+
+namespace test {
+class QuicStreamFactoryPeer;
+}  // namespace test
+
+// Encapsulates a pending request for a QuicHttpStream.
+// If the request is still pending when it is destroyed, it will
+// cancel the request with the factory.
+class NET_EXPORT_PRIVATE QuicStreamRequest {
+ public:
+  explicit QuicStreamRequest(QuicStreamFactory* factory);
+  ~QuicStreamRequest();
+
+  // For http, |is_https| is false.
+  int Request(const HostPortPair& host_port_pair,
+              bool is_https,
+              PrivacyMode privacy_mode,
+              base::StringPiece method,
+              const BoundNetLog& net_log,
+              const CompletionCallback& callback);
+
+  void OnRequestComplete(int rv);
+
+  scoped_ptr<QuicHttpStream> ReleaseStream();
+
+  void set_stream(scoped_ptr<QuicHttpStream> stream);
+
+  const BoundNetLog& net_log() const{
+    return net_log_;
+  }
+
+ private:
+  QuicStreamFactory* factory_;
+  HostPortPair host_port_pair_;
+  bool is_https_;
+  BoundNetLog net_log_;
+  CompletionCallback callback_;
+  scoped_ptr<QuicHttpStream> stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicStreamRequest);
+};
+
+// A factory for creating new QuicHttpStreams on top of a pool of
+// QuicClientSessions.
+class NET_EXPORT_PRIVATE QuicStreamFactory
+    : public NetworkChangeNotifier::IPAddressObserver,
+      public CertDatabase::Observer {
+ public:
+  QuicStreamFactory(
+      HostResolver* host_resolver,
+      ClientSocketFactory* client_socket_factory,
+      base::WeakPtr<HttpServerProperties> http_server_properties,
+      CertVerifier* cert_verifier,
+      ChannelIDService* channel_id_service,
+      TransportSecurityState* transport_security_state,
+      QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory,
+      QuicRandom* random_generator,
+      QuicClock* clock,
+      size_t max_packet_length,
+      const std::string& user_agent_id,
+      const QuicVersionVector& supported_versions,
+      bool enable_port_selection,
+      bool always_require_handshake_confirmation,
+      bool disable_connection_pooling,
+      const QuicTagVector& connection_options);
+  virtual ~QuicStreamFactory();
+
+  // Creates a new QuicHttpStream to |host_port_pair| which will be
+  // owned by |request|. |is_https| specifies if the protocol is https or not.
+  // If a matching session already exists, this method will return OK.  If no
+  // matching session exists, this will return ERR_IO_PENDING and will invoke
+  // OnRequestComplete asynchronously.
+  int Create(const HostPortPair& host_port_pair,
+             bool is_https,
+             PrivacyMode privacy_mode,
+             base::StringPiece method,
+             const BoundNetLog& net_log,
+             QuicStreamRequest* request);
+
+  // Called by a session when it becomes idle.
+  void OnIdleSession(QuicClientSession* session);
+
+  // Called by a session when it is going away and no more streams should be
+  // created on it.
+  void OnSessionGoingAway(QuicClientSession* session);
+
+  // Called by a session after it shuts down.
+  void OnSessionClosed(QuicClientSession* session);
+
+  // Called by a session whose connection has timed out.
+  void OnSessionConnectTimeout(QuicClientSession* session);
+
+  // Cancels a pending request.
+  void CancelRequest(QuicStreamRequest* request);
+
+  // Closes all current sessions.
+  void CloseAllSessions(int error);
+
+  base::Value* QuicStreamFactoryInfoToValue() const;
+
+  // Delete all cached state objects in |crypto_config_|.
+  void ClearCachedStatesInCryptoConfig();
+
+  // NetworkChangeNotifier::IPAddressObserver methods:
+
+  // Until the servers support roaming, close all connections when the local
+  // IP address changes.
+  virtual void OnIPAddressChanged() OVERRIDE;
+
+  // CertDatabase::Observer methods:
+
+  // We close all sessions when certificate database is changed.
+  virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE;
+  virtual void OnCACertChanged(const X509Certificate* cert) OVERRIDE;
+
+  bool require_confirmation() const {
+    return require_confirmation_;
+  }
+
+  void set_require_confirmation(bool require_confirmation);
+
+  QuicConnectionHelper* helper() { return helper_.get(); }
+
+  bool enable_port_selection() const { return enable_port_selection_; }
+
+  bool has_quic_server_info_factory() {
+    return quic_server_info_factory_ != NULL;
+  }
+
+  void set_quic_server_info_factory(
+      QuicServerInfoFactory* quic_server_info_factory) {
+    DCHECK(!quic_server_info_factory_);
+    quic_server_info_factory_ = quic_server_info_factory;
+  }
+
+ private:
+  class Job;
+  friend class test::QuicStreamFactoryPeer;
+
+  // The key used to find session by ip. Includes
+  // the ip address, port, and scheme.
+  struct NET_EXPORT_PRIVATE IpAliasKey {
+    IpAliasKey();
+    IpAliasKey(IPEndPoint ip_endpoint, bool is_https);
+    ~IpAliasKey();
+
+    IPEndPoint ip_endpoint;
+    bool is_https;
+
+    // Needed to be an element of std::set.
+    bool operator<(const IpAliasKey &other) const;
+    bool operator==(const IpAliasKey &other) const;
+  };
+
+  typedef std::map<QuicServerId, QuicClientSession*> SessionMap;
+  typedef std::map<QuicClientSession*, QuicServerId> SessionIdMap;
+  typedef std::set<QuicServerId> AliasSet;
+  typedef std::map<QuicClientSession*, AliasSet> SessionAliasMap;
+  typedef std::set<QuicClientSession*> SessionSet;
+  typedef std::map<IpAliasKey, SessionSet> IPAliasMap;
+  typedef std::map<QuicServerId, QuicCryptoClientConfig*> CryptoConfigMap;
+  typedef std::map<QuicServerId, Job*> JobMap;
+  typedef std::map<QuicStreamRequest*, Job*> RequestMap;
+  typedef std::set<QuicStreamRequest*> RequestSet;
+  typedef std::map<Job*, RequestSet> JobRequestsMap;
+
+  // Returns a newly created QuicHttpStream owned by the caller, if a
+  // matching session already exists.  Returns NULL otherwise.
+  scoped_ptr<QuicHttpStream> CreateIfSessionExists(const QuicServerId& key,
+                                                   const BoundNetLog& net_log);
+
+  bool OnResolution(const QuicServerId& server_id,
+                    const AddressList& address_list);
+  void OnJobComplete(Job* job, int rv);
+  bool HasActiveSession(const QuicServerId& server_id) const;
+  bool HasActiveJob(const QuicServerId& server_id) const;
+  int CreateSession(const QuicServerId& server_id,
+                    scoped_ptr<QuicServerInfo> quic_server_info,
+                    const AddressList& address_list,
+                    const BoundNetLog& net_log,
+                    QuicClientSession** session);
+  void ActivateSession(const QuicServerId& key,
+                       QuicClientSession* session);
+
+  // Initializes the cached state associated with |server_id| in
+  // |crypto_config_| with the information in |server_info|.
+  void InitializeCachedStateInCryptoConfig(
+      const QuicServerId& server_id,
+      const scoped_ptr<QuicServerInfo>& server_info);
+
+  void ProcessGoingAwaySession(QuicClientSession* session,
+                               const QuicServerId& server_id,
+                               bool was_session_active);
+
+  bool require_confirmation_;
+  HostResolver* host_resolver_;
+  ClientSocketFactory* client_socket_factory_;
+  base::WeakPtr<HttpServerProperties> http_server_properties_;
+  TransportSecurityState* transport_security_state_;
+  QuicServerInfoFactory* quic_server_info_factory_;
+  QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory_;
+  QuicRandom* random_generator_;
+  scoped_ptr<QuicClock> clock_;
+  const size_t max_packet_length_;
+
+  // The helper used for all connections.
+  scoped_ptr<QuicConnectionHelper> helper_;
+
+  // Contains owning pointers to all sessions that currently exist.
+  SessionIdMap all_sessions_;
+  // Contains non-owning pointers to currently active session
+  // (not going away session, once they're implemented).
+  SessionMap active_sessions_;
+  // Map from session to set of aliases that this session is known by.
+  SessionAliasMap session_aliases_;
+  // Map from IP address to sessions which are connected to this address.
+  IPAliasMap ip_aliases_;
+
+  // Origins which have gone away recently.
+  AliasSet gone_away_aliases_;
+
+  const QuicConfig config_;
+  QuicCryptoClientConfig crypto_config_;
+
+  JobMap active_jobs_;
+  JobRequestsMap job_requests_map_;
+  RequestMap active_requests_;
+
+  QuicVersionVector supported_versions_;
+
+  // Determine if we should consistently select a client UDP port. If false,
+  // then we will just let the OS select a random client port for each new
+  // connection.
+  bool enable_port_selection_;
+
+  // Set if we always require handshake confirmation. If true, this will
+  // introduce at least one RTT for the handshake before the client sends data.
+  bool always_require_handshake_confirmation_;
+
+  // Set if we do not want connection pooling.
+  bool disable_connection_pooling_;
+
+  // Each profile will (probably) have a unique port_seed_ value.  This value is
+  // used to help seed a pseudo-random number generator (PortSuggester) so that
+  // we consistently (within this profile) suggest the same ephemeral port when
+  // we re-connect to any given server/port.  The differences between profiles
+  // (probablistically) prevent two profiles from colliding in their ephemeral
+  // port requests.
+  uint64 port_seed_;
+
+  // Local address of socket that was created in CreateSession.
+  IPEndPoint local_address_;
+  bool check_persisted_supports_quic_;
+
+  base::WeakPtrFactory<QuicStreamFactory> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicStreamFactory);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_STREAM_FACTORY_H_
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
new file mode 100644
index 0000000..ece3e64
--- /dev/null
+++ b/net/quic/quic_stream_factory_test.cc
@@ -0,0 +1,1513 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_stream_factory.h"
+
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_server_id.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_packet_maker.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_test_utils.h"
+#include "net/ssl/channel_id_service.h"
+#include "net/ssl/default_channel_id_store.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+namespace {
+const char kDefaultServerHostName[] = "www.google.com";
+const int kDefaultServerPort = 443;
+}  // namespace anonymous
+
+class QuicStreamFactoryPeer {
+ public:
+  static QuicCryptoClientConfig* GetCryptoConfig(QuicStreamFactory* factory) {
+    return &factory->crypto_config_;
+  }
+
+  static bool HasActiveSession(QuicStreamFactory* factory,
+                               const HostPortPair& host_port_pair,
+                               bool is_https) {
+    QuicServerId server_id(host_port_pair, is_https, PRIVACY_MODE_DISABLED);
+    return factory->HasActiveSession(server_id);
+  }
+
+  static QuicClientSession* GetActiveSession(
+      QuicStreamFactory* factory,
+      const HostPortPair& host_port_pair,
+      bool is_https) {
+    QuicServerId server_id(host_port_pair, is_https, PRIVACY_MODE_DISABLED);
+    DCHECK(factory->HasActiveSession(server_id));
+    return factory->active_sessions_[server_id];
+  }
+
+  static scoped_ptr<QuicHttpStream> CreateIfSessionExists(
+      QuicStreamFactory* factory,
+      const HostPortPair& host_port_pair,
+      bool is_https,
+      const BoundNetLog& net_log) {
+    QuicServerId server_id(host_port_pair, is_https, PRIVACY_MODE_DISABLED);
+    return factory->CreateIfSessionExists(server_id, net_log);
+  }
+
+  static bool IsLiveSession(QuicStreamFactory* factory,
+                            QuicClientSession* session) {
+    for (QuicStreamFactory::SessionIdMap::iterator it =
+             factory->all_sessions_.begin();
+         it != factory->all_sessions_.end(); ++it) {
+      if (it->first == session)
+        return true;
+    }
+    return false;
+  }
+
+  static void DisableConnectionPooling(QuicStreamFactory* factory) {
+    factory->disable_connection_pooling_ = true;
+  }
+};
+
+class QuicStreamFactoryTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+  QuicStreamFactoryTest()
+      : random_generator_(0),
+        clock_(new MockClock()),
+        maker_(GetParam(), 0, clock_),
+        cert_verifier_(CertVerifier::CreateDefault()),
+        channel_id_service_(
+            new ChannelIDService(new DefaultChannelIDStore(nullptr),
+                                 base::MessageLoopProxy::current())),
+        factory_(&host_resolver_,
+                 &socket_factory_,
+                 base::WeakPtr<HttpServerProperties>(),
+                 cert_verifier_.get(),
+                 channel_id_service_.get(),
+                 &transport_security_state_,
+                 &crypto_client_stream_factory_,
+                 &random_generator_,
+                 clock_,
+                 kDefaultMaxPacketSize,
+                 std::string(),
+                 SupportedVersions(GetParam()),
+                 /*enable_port_selection=*/true,
+                 /*always_require_handshake_confirmation=*/false,
+                 /*disable_connection_pooling=*/false,
+                 QuicTagVector()),
+        host_port_pair_(kDefaultServerHostName, kDefaultServerPort),
+        is_https_(false),
+        privacy_mode_(PRIVACY_MODE_DISABLED) {
+    factory_.set_require_confirmation(false);
+    clock_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+  }
+
+  scoped_ptr<QuicHttpStream> CreateIfSessionExists(
+      const HostPortPair& host_port_pair,
+      const BoundNetLog& net_log) {
+    return QuicStreamFactoryPeer::CreateIfSessionExists(
+        &factory_, host_port_pair, false, net_log_);
+  }
+
+  int GetSourcePortForNewSession(const HostPortPair& destination) {
+    return GetSourcePortForNewSessionInner(destination, false);
+  }
+
+  int GetSourcePortForNewSessionAndGoAway(
+      const HostPortPair& destination) {
+    return GetSourcePortForNewSessionInner(destination, true);
+  }
+
+  int GetSourcePortForNewSessionInner(const HostPortPair& destination,
+                                      bool goaway_received) {
+    // Should only be called if there is no active session for this destination.
+    EXPECT_EQ(nullptr, CreateIfSessionExists(destination, net_log_).get());
+    size_t socket_count = socket_factory_.udp_client_sockets().size();
+
+    MockRead reads[] = {
+      MockRead(ASYNC, OK, 0)  // EOF
+    };
+    DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+    socket_data.StopAfter(1);
+    socket_factory_.AddSocketDataProvider(&socket_data);
+
+    QuicStreamRequest request(&factory_);
+    EXPECT_EQ(ERR_IO_PENDING,
+              request.Request(destination,
+                              is_https_,
+                              privacy_mode_,
+                              "GET",
+                              net_log_,
+                              callback_.callback()));
+
+    EXPECT_EQ(OK, callback_.WaitForResult());
+    scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+    EXPECT_TRUE(stream.get());
+    stream.reset();
+
+    QuicClientSession* session = QuicStreamFactoryPeer::GetActiveSession(
+        &factory_, destination, is_https_);
+
+    if (socket_count + 1 != socket_factory_.udp_client_sockets().size()) {
+      EXPECT_TRUE(false);
+      return 0;
+    }
+
+    IPEndPoint endpoint;
+    socket_factory_.
+        udp_client_sockets()[socket_count]->GetLocalAddress(&endpoint);
+    int port = endpoint.port();
+    if (goaway_received) {
+      QuicGoAwayFrame goaway(QUIC_NO_ERROR, 1, "");
+      session->OnGoAway(goaway);
+    }
+
+    factory_.OnSessionClosed(session);
+    EXPECT_EQ(nullptr, CreateIfSessionExists(destination, net_log_).get());
+    EXPECT_TRUE(socket_data.at_read_eof());
+    EXPECT_TRUE(socket_data.at_write_eof());
+    return port;
+  }
+
+  scoped_ptr<QuicEncryptedPacket> ConstructRstPacket() {
+    QuicStreamId stream_id = kClientDataStreamId1;
+    return maker_.MakeRstPacket(
+        1, true, stream_id,
+        AdjustErrorForVersion(QUIC_RST_FLOW_CONTROL_ACCOUNTING, GetParam()));
+  }
+
+  MockHostResolver host_resolver_;
+  DeterministicMockClientSocketFactory socket_factory_;
+  MockCryptoClientStreamFactory crypto_client_stream_factory_;
+  MockRandom random_generator_;
+  MockClock* clock_;  // Owned by factory_.
+  QuicTestPacketMaker maker_;
+  scoped_ptr<CertVerifier> cert_verifier_;
+  scoped_ptr<ChannelIDService> channel_id_service_;
+  TransportSecurityState transport_security_state_;
+  QuicStreamFactory factory_;
+  HostPortPair host_port_pair_;
+  bool is_https_;
+  PrivacyMode privacy_mode_;
+  BoundNetLog net_log_;
+  TestCompletionCallback callback_;
+};
+
+INSTANTIATE_TEST_CASE_P(Version, QuicStreamFactoryTest,
+                        ::testing::ValuesIn(QuicSupportedVersions()));
+
+TEST_P(QuicStreamFactoryTest, CreateIfSessionExists) {
+  EXPECT_EQ(nullptr, CreateIfSessionExists(host_port_pair_, net_log_).get());
+}
+
+TEST_P(QuicStreamFactoryTest, Create) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  // Will reset stream 3.
+  stream = CreateIfSessionExists(host_port_pair_, net_log_);
+  EXPECT_TRUE(stream.get());
+
+  // TODO(rtenneti): We should probably have a tests that HTTP and HTTPS result
+  // in streams on different sessions.
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  stream = request2.ReleaseStream();  // Will reset stream 5.
+  stream.reset();  // Will reset stream 7.
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, CreateZeroRtt) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  crypto_client_stream_factory_.set_handshake_mode(
+      MockCryptoClientStream::ZERO_RTT);
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(host_port_pair_.host(),
+                                           "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(OK,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, CreateZeroRttPost) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  crypto_client_stream_factory_.set_handshake_mode(
+      MockCryptoClientStream::ZERO_RTT);
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(host_port_pair_.host(),
+                                           "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  // Posts require handshake confirmation, so this will return asynchronously.
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "POST",
+                            net_log_,
+                            callback_.callback()));
+
+  // Confirm the handshake and verify that the stream is created.
+  crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
+      QuicSession::HANDSHAKE_CONFIRMED);
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, CreateHttpVsHttps) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             !is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  stream = request2.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+  stream.reset();
+
+  EXPECT_NE(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, host_port_pair_, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, host_port_pair_, !is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, Pooling) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  HostPortPair server2("mail.google.com", kDefaultServerPort);
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(
+      kDefaultServerHostName, "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(
+      "mail.google.com", "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(OK,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_EQ(
+      QuicStreamFactoryPeer::GetActiveSession(
+          &factory_, host_port_pair_, is_https_),
+      QuicStreamFactoryPeer::GetActiveSession(&factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoPoolingIfDisabled) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server2("mail.google.com", kDefaultServerPort);
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(
+      kDefaultServerHostName, "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(
+      "mail.google.com", "192.168.0.1", "");
+
+  // Disable connection pooling.
+  QuicStreamFactoryPeer::DisableConnectionPooling(&factory_);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(OK,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_NE(
+      QuicStreamFactoryPeer::GetActiveSession(
+          &factory_, host_port_pair_, is_https_),
+      QuicStreamFactoryPeer::GetActiveSession(&factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoPoolingAfterGoAway) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server2("mail.google.com", kDefaultServerPort);
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(
+      kDefaultServerHostName, "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(
+      "mail.google.com", "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(OK,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  factory_.OnSessionGoingAway(QuicStreamFactoryPeer::GetActiveSession(
+      &factory_, host_port_pair_, is_https_));
+  EXPECT_FALSE(QuicStreamFactoryPeer::HasActiveSession(
+      &factory_, host_port_pair_, is_https_));
+  EXPECT_FALSE(QuicStreamFactoryPeer::HasActiveSession(
+      &factory_, server2, is_https_));
+
+  TestCompletionCallback callback3;
+  QuicStreamRequest request3(&factory_);
+  EXPECT_EQ(OK,
+            request3.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback3.callback()));
+  scoped_ptr<QuicHttpStream> stream3 = request3.ReleaseStream();
+  EXPECT_TRUE(stream3.get());
+
+  EXPECT_TRUE(QuicStreamFactoryPeer::HasActiveSession(
+      &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, HttpsPooling) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.example.org", 443);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org (server2)
+  //   www.example.com
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  verify_details.cert_verify_result.is_issued_by_known_root = true;
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_EQ(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoHttpsPoolingIfDisabled) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.example.org", 443);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org (server2)
+  //   www.example.com
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  verify_details.cert_verify_result.is_issued_by_known_root = true;
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  // Disable connection pooling.
+  QuicStreamFactoryPeer::DisableConnectionPooling(&factory_);
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_NE(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoHttpsPoolingWithCertMismatch) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.google.com", 443);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org
+  //   www.example.com
+  // But is not valid for mail.google.com (server2).
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_NE(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, HttpsPoolingWithMatchingPins) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.example.org", 443);
+  uint8 primary_pin = 1;
+  uint8 backup_pin = 2;
+  test::AddPin(&transport_security_state_, "mail.example.org", primary_pin,
+               backup_pin);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org (server2)
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  verify_details.cert_verify_result.is_issued_by_known_root = true;
+  verify_details.cert_verify_result.public_key_hashes.push_back(
+      test::GetTestHashValue(primary_pin));
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_EQ(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoHttpsPoolingWithMatchingPinsIfDisabled) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.example.org", 443);
+  uint8 primary_pin = 1;
+  uint8 backup_pin = 2;
+  test::AddPin(&transport_security_state_, "mail.example.org", primary_pin,
+               backup_pin);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org (server2)
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  verify_details.cert_verify_result.is_issued_by_known_root = true;
+  verify_details.cert_verify_result.public_key_hashes.push_back(
+      test::GetTestHashValue(primary_pin));
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  // Disable connection pooling.
+  QuicStreamFactoryPeer::DisableConnectionPooling(&factory_);
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_NE(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, NoHttpsPoolingWithDifferentPins) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data1(reads, arraysize(reads), nullptr, 0);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data1.StopAfter(1);
+  socket_data2.StopAfter(1);
+
+  HostPortPair server1("www.example.org", 443);
+  HostPortPair server2("mail.example.org", 443);
+  uint8 primary_pin = 1;
+  uint8 backup_pin = 2;
+  uint8 bad_pin = 3;
+  test::AddPin(&transport_security_state_, "mail.example.org", primary_pin,
+               backup_pin);
+
+  // Load a cert that is valid for:
+  //   www.example.org (server1)
+  //   mail.example.org (server2)
+  base::FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get());
+  ProofVerifyDetailsChromium verify_details;
+  verify_details.cert_verify_result.verified_cert = test_cert;
+  verify_details.cert_verify_result.is_issued_by_known_root = true;
+  verify_details.cert_verify_result.public_key_hashes.push_back(
+      test::GetTestHashValue(bad_pin));
+  crypto_client_stream_factory_.set_proof_verify_details(&verify_details);
+
+
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule(server1.host(), "192.168.0.1", "");
+  host_resolver_.rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
+
+  QuicStreamRequest request(&factory_);
+  is_https_ = true;
+  EXPECT_EQ(OK,
+            request.Request(server1,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  TestCompletionCallback callback;
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(OK,
+            request2.Request(server2,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_NE(QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server1, is_https_),
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, server2, is_https_));
+
+  EXPECT_TRUE(socket_data1.at_read_eof());
+  EXPECT_TRUE(socket_data1.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, Goaway) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_data.StopAfter(1);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  DeterministicSocketData socket_data2(reads, arraysize(reads), nullptr, 0);
+  socket_data2.StopAfter(1);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream.get());
+
+  // Mark the session as going away.  Ensure that while it is still alive
+  // that it is no longer active.
+  QuicClientSession* session = QuicStreamFactoryPeer::GetActiveSession(
+      &factory_, host_port_pair_, is_https_);
+  factory_.OnSessionGoingAway(session);
+  EXPECT_EQ(true, QuicStreamFactoryPeer::IsLiveSession(&factory_, session));
+  EXPECT_FALSE(QuicStreamFactoryPeer::HasActiveSession(
+      &factory_, host_port_pair_, is_https_));
+  EXPECT_EQ(nullptr, CreateIfSessionExists(host_port_pair_, net_log_).get());
+
+  // Create a new request for the same destination and verify that a
+  // new session is created.
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream2 = request2.ReleaseStream();
+  EXPECT_TRUE(stream2.get());
+
+  EXPECT_TRUE(QuicStreamFactoryPeer::HasActiveSession(&factory_,
+                                                      host_port_pair_,
+                                                      is_https_));
+  EXPECT_NE(session,
+            QuicStreamFactoryPeer::GetActiveSession(
+                &factory_, host_port_pair_, is_https_));
+  EXPECT_EQ(true, QuicStreamFactoryPeer::IsLiveSession(&factory_, session));
+
+  stream2.reset();
+  stream.reset();
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, MaxOpenStream) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  QuicStreamId stream_id = kClientDataStreamId1;
+  scoped_ptr<QuicEncryptedPacket> rst(
+      maker_.MakeRstPacket(1, true, stream_id, QUIC_STREAM_CANCELLED));
+  MockWrite writes[] = {
+    MockWrite(ASYNC, rst->data(), rst->length(), 1),
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads),
+                                      writes, arraysize(writes));
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  HttpRequestInfo request_info;
+  std::vector<QuicHttpStream*> streams;
+  // The MockCryptoClientStream sets max_open_streams to be
+  // 2 * kDefaultMaxStreamsPerConnection.
+  for (size_t i = 0; i < 2 * kDefaultMaxStreamsPerConnection; i++) {
+    QuicStreamRequest request(&factory_);
+    int rv = request.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback());
+    if (i == 0) {
+      EXPECT_EQ(ERR_IO_PENDING, rv);
+      EXPECT_EQ(OK, callback_.WaitForResult());
+    } else {
+      EXPECT_EQ(OK, rv);
+    }
+    scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+    EXPECT_TRUE(stream);
+    EXPECT_EQ(OK, stream->InitializeStream(
+        &request_info, DEFAULT_PRIORITY, net_log_, CompletionCallback()));
+    streams.push_back(stream.release());
+  }
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(OK,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            CompletionCallback()));
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  EXPECT_TRUE(stream);
+  EXPECT_EQ(ERR_IO_PENDING, stream->InitializeStream(
+        &request_info, DEFAULT_PRIORITY, net_log_, callback_.callback()));
+
+  // Close the first stream.
+  streams.front()->Close(false);
+
+  ASSERT_TRUE(callback_.have_result());
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  STLDeleteElements(&streams);
+}
+
+TEST_P(QuicStreamFactoryTest, ResolutionErrorInCreate) {
+  DeterministicSocketData socket_data(nullptr, 0, nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+
+  host_resolver_.rules()->AddSimulatedFailure(kDefaultServerHostName);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, ConnectErrorInCreate) {
+  MockConnect connect(SYNCHRONOUS, ERR_ADDRESS_IN_USE);
+  DeterministicSocketData socket_data(nullptr, 0, nullptr, 0);
+  socket_data.set_connect_data(connect);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(ERR_ADDRESS_IN_USE, callback_.WaitForResult());
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, CancelCreate) {
+  MockRead reads[] = {
+    MockRead(ASYNC, OK, 0)  // EOF
+  };
+  DeterministicSocketData socket_data(reads, arraysize(reads), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  {
+    QuicStreamRequest request(&factory_);
+    EXPECT_EQ(ERR_IO_PENDING,
+              request.Request(host_port_pair_,
+                              is_https_,
+                              privacy_mode_,
+                              "GET",
+                              net_log_,
+                              callback_.callback()));
+  }
+
+  socket_data.StopAfter(1);
+  base::RunLoop run_loop;
+  run_loop.RunUntilIdle();
+
+  scoped_ptr<QuicHttpStream> stream(
+      CreateIfSessionExists(host_port_pair_, net_log_));
+  EXPECT_TRUE(stream.get());
+  stream.reset();
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, CreateConsistentEphemeralPort) {
+  // Sequentially connect to the default host, then another host, and then the
+  // default host.  Verify that the default host gets a consistent ephemeral
+  // port, that is different from the other host's connection.
+
+  std::string other_server_name = "other.google.com";
+  EXPECT_NE(kDefaultServerHostName, other_server_name);
+  HostPortPair host_port_pair2(other_server_name, kDefaultServerPort);
+
+  int original_port = GetSourcePortForNewSession(host_port_pair_);
+  EXPECT_NE(original_port, GetSourcePortForNewSession(host_port_pair2));
+  EXPECT_EQ(original_port, GetSourcePortForNewSession(host_port_pair_));
+}
+
+TEST_P(QuicStreamFactoryTest, GoAwayDisablesConsistentEphemeralPort) {
+  // Get a session to the host using the port suggester.
+  int original_port =
+      GetSourcePortForNewSessionAndGoAway(host_port_pair_);
+  // Verify that the port is different after the goaway.
+  EXPECT_NE(original_port, GetSourcePortForNewSession(host_port_pair_));
+  // Since the previous session did not goaway we should see the original port.
+  EXPECT_EQ(original_port, GetSourcePortForNewSession(host_port_pair_));
+}
+
+TEST_P(QuicStreamFactoryTest, CloseAllSessions) {
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  scoped_ptr<QuicEncryptedPacket> rst(ConstructRstPacket());
+  std::vector<MockWrite> writes;
+  writes.push_back(MockWrite(ASYNC, rst->data(), rst->length(), 1));
+  DeterministicSocketData socket_data(reads, arraysize(reads),
+                                      writes.empty() ? nullptr  : &writes[0],
+                                      writes.size());
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  MockRead reads2[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data2.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  HttpRequestInfo request_info;
+  EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+                                         DEFAULT_PRIORITY,
+                                         net_log_, CompletionCallback()));
+
+  // Close the session and verify that stream saw the error.
+  factory_.CloseAllSessions(ERR_INTERNET_DISCONNECTED);
+  EXPECT_EQ(ERR_INTERNET_DISCONNECTED,
+            stream->ReadResponseHeaders(callback_.callback()));
+
+  // Now attempting to request a stream to the same origin should create
+  // a new session.
+
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  stream = request2.ReleaseStream();
+  stream.reset();  // Will reset stream 3.
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, OnIPAddressChanged) {
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  scoped_ptr<QuicEncryptedPacket> rst(ConstructRstPacket());
+  std::vector<MockWrite> writes;
+  writes.push_back(MockWrite(ASYNC, rst->data(), rst->length(), 1));
+  DeterministicSocketData socket_data(reads, arraysize(reads),
+                                      writes.empty() ? nullptr  : &writes[0],
+                                      writes.size());
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  MockRead reads2[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data2.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  HttpRequestInfo request_info;
+  EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+                                         DEFAULT_PRIORITY,
+                                         net_log_, CompletionCallback()));
+
+  // Change the IP address and verify that stream saw the error.
+  factory_.OnIPAddressChanged();
+  EXPECT_EQ(ERR_NETWORK_CHANGED,
+            stream->ReadResponseHeaders(callback_.callback()));
+  EXPECT_TRUE(factory_.require_confirmation());
+
+  // Now attempting to request a stream to the same origin should create
+  // a new session.
+
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  stream = request2.ReleaseStream();
+  stream.reset();  // Will reset stream 3.
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, OnCertAdded) {
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  scoped_ptr<QuicEncryptedPacket> rst(ConstructRstPacket());
+  std::vector<MockWrite> writes;
+  writes.push_back(MockWrite(ASYNC, rst->data(), rst->length(), 1));
+  DeterministicSocketData socket_data(reads, arraysize(reads),
+                                      writes.empty() ? nullptr  : &writes[0],
+                                      writes.size());
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  MockRead reads2[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data2.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  HttpRequestInfo request_info;
+  EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+                                         DEFAULT_PRIORITY,
+                                         net_log_, CompletionCallback()));
+
+  // Add a cert and verify that stream saw the event.
+  factory_.OnCertAdded(nullptr);
+  EXPECT_EQ(ERR_CERT_DATABASE_CHANGED,
+            stream->ReadResponseHeaders(callback_.callback()));
+  EXPECT_FALSE(factory_.require_confirmation());
+
+  // Now attempting to request a stream to the same origin should create
+  // a new session.
+
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  stream = request2.ReleaseStream();
+  stream.reset();  // Will reset stream 3.
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, OnCACertChanged) {
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  scoped_ptr<QuicEncryptedPacket> rst(ConstructRstPacket());
+  std::vector<MockWrite> writes;
+  writes.push_back(MockWrite(ASYNC, rst->data(), rst->length(), 1));
+  DeterministicSocketData socket_data(reads, arraysize(reads),
+                                      writes.empty() ? nullptr  : &writes[0],
+                                      writes.size());
+  socket_factory_.AddSocketDataProvider(&socket_data);
+  socket_data.StopAfter(1);
+
+  MockRead reads2[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData socket_data2(reads2, arraysize(reads2), nullptr, 0);
+  socket_factory_.AddSocketDataProvider(&socket_data2);
+  socket_data2.StopAfter(1);
+
+  QuicStreamRequest request(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(host_port_pair_,
+                            is_https_,
+                            privacy_mode_,
+                            "GET",
+                            net_log_,
+                            callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+  HttpRequestInfo request_info;
+  EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+                                         DEFAULT_PRIORITY,
+                                         net_log_, CompletionCallback()));
+
+  // Change the CA cert and verify that stream saw the event.
+  factory_.OnCACertChanged(nullptr);
+  EXPECT_EQ(ERR_CERT_DATABASE_CHANGED,
+            stream->ReadResponseHeaders(callback_.callback()));
+  EXPECT_FALSE(factory_.require_confirmation());
+
+  // Now attempting to request a stream to the same origin should create
+  // a new session.
+
+  QuicStreamRequest request2(&factory_);
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(host_port_pair_,
+                             is_https_,
+                             privacy_mode_,
+                             "GET",
+                             net_log_,
+                             callback_.callback()));
+
+  EXPECT_EQ(OK, callback_.WaitForResult());
+  stream = request2.ReleaseStream();
+  stream.reset();  // Will reset stream 3.
+
+  EXPECT_TRUE(socket_data.at_read_eof());
+  EXPECT_TRUE(socket_data.at_write_eof());
+  EXPECT_TRUE(socket_data2.at_read_eof());
+  EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_P(QuicStreamFactoryTest, SharedCryptoConfig) {
+  vector<string> cannoncial_suffixes;
+  cannoncial_suffixes.push_back(string(".c.youtube.com"));
+  cannoncial_suffixes.push_back(string(".googlevideo.com"));
+
+  for (unsigned i = 0; i < cannoncial_suffixes.size(); ++i) {
+    string r1_host_name("r1");
+    string r2_host_name("r2");
+    r1_host_name.append(cannoncial_suffixes[i]);
+    r2_host_name.append(cannoncial_suffixes[i]);
+
+    HostPortPair host_port_pair1(r1_host_name, 80);
+    QuicCryptoClientConfig* crypto_config =
+        QuicStreamFactoryPeer::GetCryptoConfig(&factory_);
+    QuicServerId server_id1(host_port_pair1, is_https_, privacy_mode_);
+    QuicCryptoClientConfig::CachedState* cached1 =
+        crypto_config->LookupOrCreate(server_id1);
+    EXPECT_FALSE(cached1->proof_valid());
+    EXPECT_TRUE(cached1->source_address_token().empty());
+
+    // Mutate the cached1 to have different data.
+    // TODO(rtenneti): mutate other members of CachedState.
+    cached1->set_source_address_token(r1_host_name);
+    cached1->SetProofValid();
+
+    HostPortPair host_port_pair2(r2_host_name, 80);
+    QuicServerId server_id2(host_port_pair2, is_https_, privacy_mode_);
+    QuicCryptoClientConfig::CachedState* cached2 =
+        crypto_config->LookupOrCreate(server_id2);
+    EXPECT_EQ(cached1->source_address_token(), cached2->source_address_token());
+    EXPECT_TRUE(cached2->proof_valid());
+  }
+}
+
+TEST_P(QuicStreamFactoryTest, CryptoConfigWhenProofIsInvalid) {
+  vector<string> cannoncial_suffixes;
+  cannoncial_suffixes.push_back(string(".c.youtube.com"));
+  cannoncial_suffixes.push_back(string(".googlevideo.com"));
+
+  for (unsigned i = 0; i < cannoncial_suffixes.size(); ++i) {
+    string r3_host_name("r3");
+    string r4_host_name("r4");
+    r3_host_name.append(cannoncial_suffixes[i]);
+    r4_host_name.append(cannoncial_suffixes[i]);
+
+    HostPortPair host_port_pair1(r3_host_name, 80);
+    QuicCryptoClientConfig* crypto_config =
+        QuicStreamFactoryPeer::GetCryptoConfig(&factory_);
+    QuicServerId server_id1(host_port_pair1, is_https_, privacy_mode_);
+    QuicCryptoClientConfig::CachedState* cached1 =
+        crypto_config->LookupOrCreate(server_id1);
+    EXPECT_FALSE(cached1->proof_valid());
+    EXPECT_TRUE(cached1->source_address_token().empty());
+
+    // Mutate the cached1 to have different data.
+    // TODO(rtenneti): mutate other members of CachedState.
+    cached1->set_source_address_token(r3_host_name);
+    cached1->SetProofInvalid();
+
+    HostPortPair host_port_pair2(r4_host_name, 80);
+    QuicServerId server_id2(host_port_pair2, is_https_, privacy_mode_);
+    QuicCryptoClientConfig::CachedState* cached2 =
+        crypto_config->LookupOrCreate(server_id2);
+    EXPECT_NE(cached1->source_address_token(), cached2->source_address_token());
+    EXPECT_TRUE(cached2->source_address_token().empty());
+    EXPECT_FALSE(cached2->proof_valid());
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_stream_sequencer.cc b/net/quic/quic_stream_sequencer.cc
new file mode 100644
index 0000000..5df81e5
--- /dev/null
+++ b/net/quic/quic_stream_sequencer.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_stream_sequencer.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/metrics/sparse_histogram.h"
+#include "net/quic/reliable_quic_stream.h"
+
+using std::make_pair;
+using std::min;
+using std::numeric_limits;
+
+namespace net {
+
+QuicStreamSequencer::QuicStreamSequencer(ReliableQuicStream* quic_stream)
+    : stream_(quic_stream),
+      num_bytes_consumed_(0),
+      close_offset_(numeric_limits<QuicStreamOffset>::max()),
+      blocked_(false),
+      num_bytes_buffered_(0),
+      num_frames_received_(0),
+      num_duplicate_frames_received_(0) {
+}
+
+QuicStreamSequencer::~QuicStreamSequencer() {
+}
+
+void QuicStreamSequencer::OnStreamFrame(const QuicStreamFrame& frame) {
+  ++num_frames_received_;
+  if (IsDuplicate(frame)) {
+    ++num_duplicate_frames_received_;
+    // Silently ignore duplicates.
+    return;
+  }
+
+  if (FrameOverlapsBufferedData(frame)) {
+    stream_->CloseConnectionWithDetails(
+        QUIC_INVALID_STREAM_FRAME, "Stream frame overlaps with buffered data.");
+    return;
+  }
+
+  QuicStreamOffset byte_offset = frame.offset;
+  size_t data_len = frame.data.TotalBufferSize();
+  if (data_len == 0 && !frame.fin) {
+    // Stream frames must have data or a fin flag.
+    stream_->CloseConnectionWithDetails(QUIC_INVALID_STREAM_FRAME,
+                                        "Empty stream frame without FIN set.");
+    return;
+  }
+
+  if (frame.fin) {
+    CloseStreamAtOffset(frame.offset + data_len);
+    if (data_len == 0) {
+      return;
+    }
+  }
+
+  IOVector data;
+  data.AppendIovec(frame.data.iovec(), frame.data.Size());
+
+  // If the frame has arrived in-order then we can process it immediately, only
+  // buffering if the stream is unable to process it.
+  if (!blocked_ && byte_offset == num_bytes_consumed_) {
+    DVLOG(1) << "Processing byte offset " << byte_offset;
+    size_t bytes_consumed = 0;
+    for (size_t i = 0; i < data.Size(); ++i) {
+      bytes_consumed += stream_->ProcessRawData(
+          static_cast<char*>(data.iovec()[i].iov_base),
+          data.iovec()[i].iov_len);
+    }
+    num_bytes_consumed_ += bytes_consumed;
+    stream_->AddBytesConsumed(bytes_consumed);
+
+    if (MaybeCloseStream()) {
+      return;
+    }
+    if (bytes_consumed > data_len) {
+      stream_->Reset(QUIC_ERROR_PROCESSING_STREAM);
+      return;
+    } else if (bytes_consumed == data_len) {
+      FlushBufferedFrames();
+      return;  // it's safe to ack this frame.
+    } else {
+      // Set ourselves up to buffer what's left.
+      data_len -= bytes_consumed;
+      data.Consume(bytes_consumed);
+      byte_offset += bytes_consumed;
+    }
+  }
+
+  // Buffer any remaining data to be consumed by the stream when ready.
+  for (size_t i = 0; i < data.Size(); ++i) {
+    DVLOG(1) << "Buffering stream data at offset " << byte_offset;
+    const iovec& iov = data.iovec()[i];
+    buffered_frames_.insert(make_pair(
+        byte_offset, string(static_cast<char*>(iov.iov_base), iov.iov_len)));
+    byte_offset += iov.iov_len;
+    num_bytes_buffered_ += iov.iov_len;
+  }
+  return;
+}
+
+void QuicStreamSequencer::CloseStreamAtOffset(QuicStreamOffset offset) {
+  const QuicStreamOffset kMaxOffset = numeric_limits<QuicStreamOffset>::max();
+
+  // If we have a scheduled termination or close, any new offset should match
+  // it.
+  if (close_offset_ != kMaxOffset && offset != close_offset_) {
+    stream_->Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+    return;
+  }
+
+  close_offset_ = offset;
+
+  MaybeCloseStream();
+}
+
+bool QuicStreamSequencer::MaybeCloseStream() {
+  if (!blocked_ && IsClosed()) {
+    DVLOG(1) << "Passing up termination, as we've processed "
+             << num_bytes_consumed_ << " of " << close_offset_
+             << " bytes.";
+    // Technically it's an error if num_bytes_consumed isn't exactly
+    // equal, but error handling seems silly at this point.
+    stream_->OnFinRead();
+    buffered_frames_.clear();
+    num_bytes_buffered_ = 0;
+    return true;
+  }
+  return false;
+}
+
+int QuicStreamSequencer::GetReadableRegions(iovec* iov, size_t iov_len) {
+  DCHECK(!blocked_);
+  FrameMap::iterator it = buffered_frames_.begin();
+  size_t index = 0;
+  QuicStreamOffset offset = num_bytes_consumed_;
+  while (it != buffered_frames_.end() && index < iov_len) {
+    if (it->first != offset) return index;
+
+    iov[index].iov_base = static_cast<void*>(
+        const_cast<char*>(it->second.data()));
+    iov[index].iov_len = it->second.size();
+    offset += it->second.size();
+
+    ++index;
+    ++it;
+  }
+  return index;
+}
+
+int QuicStreamSequencer::Readv(const struct iovec* iov, size_t iov_len) {
+  DCHECK(!blocked_);
+  FrameMap::iterator it = buffered_frames_.begin();
+  size_t iov_index = 0;
+  size_t iov_offset = 0;
+  size_t frame_offset = 0;
+  size_t initial_bytes_consumed = num_bytes_consumed_;
+
+  while (iov_index < iov_len &&
+         it != buffered_frames_.end() &&
+         it->first == num_bytes_consumed_) {
+    int bytes_to_read = min(iov[iov_index].iov_len - iov_offset,
+                            it->second.size() - frame_offset);
+
+    char* iov_ptr = static_cast<char*>(iov[iov_index].iov_base) + iov_offset;
+    memcpy(iov_ptr,
+           it->second.data() + frame_offset, bytes_to_read);
+    frame_offset += bytes_to_read;
+    iov_offset += bytes_to_read;
+
+    if (iov[iov_index].iov_len == iov_offset) {
+      // We've filled this buffer.
+      iov_offset = 0;
+      ++iov_index;
+    }
+    if (it->second.size() == frame_offset) {
+      // We've copied this whole frame
+      RecordBytesConsumed(it->second.size());
+      buffered_frames_.erase(it);
+      it = buffered_frames_.begin();
+      frame_offset = 0;
+    }
+  }
+  // We've finished copying.  If we have a partial frame, update it.
+  if (frame_offset != 0) {
+    buffered_frames_.insert(
+        make_pair(it->first + frame_offset, it->second.substr(frame_offset)));
+    buffered_frames_.erase(buffered_frames_.begin());
+    RecordBytesConsumed(frame_offset);
+  }
+  return num_bytes_consumed_ - initial_bytes_consumed;
+}
+
+bool QuicStreamSequencer::HasBytesToRead() const {
+  FrameMap::const_iterator it = buffered_frames_.begin();
+
+  return it != buffered_frames_.end() && it->first == num_bytes_consumed_;
+}
+
+bool QuicStreamSequencer::IsClosed() const {
+  return num_bytes_consumed_ >= close_offset_;
+}
+
+bool QuicStreamSequencer::FrameOverlapsBufferedData(
+    const QuicStreamFrame& frame) const {
+  if (buffered_frames_.empty()) {
+    return false;
+  }
+
+  FrameMap::const_iterator next_frame =
+      buffered_frames_.lower_bound(frame.offset);
+  // Duplicate frames should have been dropped in IsDuplicate.
+  DCHECK(next_frame == buffered_frames_.end() ||
+         next_frame->first != frame.offset);
+
+  // If there is a buffered frame with a higher starting offset, then we check
+  // to see if the new frame runs into the higher frame.
+  if (next_frame != buffered_frames_.end() &&
+      (frame.offset + frame.data.TotalBufferSize()) > next_frame->first) {
+    DVLOG(1) << "New frame overlaps next frame: " << frame.offset << " + "
+             << frame.data.TotalBufferSize() << " > " << next_frame->first;
+    return true;
+  }
+
+  // If there is a buffered frame with a lower starting offset, then we check
+  // to see if the buffered frame runs into the new frame.
+  if (next_frame != buffered_frames_.begin()) {
+    FrameMap::const_iterator preceeding_frame = --next_frame;
+    QuicStreamOffset offset = preceeding_frame->first;
+    uint64 data_length = preceeding_frame->second.length();
+    if ((offset + data_length) > frame.offset) {
+      DVLOG(1) << "Preceeding frame overlaps new frame: " << offset << " + "
+               << data_length << " > " << frame.offset;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool QuicStreamSequencer::IsDuplicate(const QuicStreamFrame& frame) const {
+  // A frame is duplicate if the frame offset is smaller than our bytes consumed
+  // or we have stored the frame in our map.
+  // TODO(pwestin): Is it possible that a new frame contain more data even if
+  // the offset is the same?
+  return frame.offset < num_bytes_consumed_ ||
+      buffered_frames_.find(frame.offset) != buffered_frames_.end();
+}
+
+void QuicStreamSequencer::SetBlockedUntilFlush() {
+  blocked_ = true;
+}
+
+void QuicStreamSequencer::FlushBufferedFrames() {
+  blocked_ = false;
+  FrameMap::iterator it = buffered_frames_.find(num_bytes_consumed_);
+  while (it != buffered_frames_.end()) {
+    DVLOG(1) << "Flushing buffered packet at offset " << it->first;
+    string* data = &it->second;
+    size_t bytes_consumed = stream_->ProcessRawData(data->c_str(),
+                                                    data->size());
+    RecordBytesConsumed(bytes_consumed);
+    if (MaybeCloseStream()) {
+      return;
+    }
+    if (bytes_consumed > data->size()) {
+      stream_->Reset(QUIC_ERROR_PROCESSING_STREAM);  // Programming error
+      return;
+    } else if (bytes_consumed == data->size()) {
+      buffered_frames_.erase(it);
+      it = buffered_frames_.find(num_bytes_consumed_);
+    } else {
+      string new_data = it->second.substr(bytes_consumed);
+      buffered_frames_.erase(it);
+      buffered_frames_.insert(make_pair(num_bytes_consumed_, new_data));
+      return;
+    }
+  }
+  MaybeCloseStream();
+}
+
+void QuicStreamSequencer::RecordBytesConsumed(size_t bytes_consumed) {
+  num_bytes_consumed_ += bytes_consumed;
+  num_bytes_buffered_ -= bytes_consumed;
+
+  stream_->AddBytesConsumed(bytes_consumed);
+}
+
+}  // namespace net
diff --git a/net/quic/quic_stream_sequencer.h b/net/quic/quic_stream_sequencer.h
new file mode 100644
index 0000000..5da2c0e
--- /dev/null
+++ b/net/quic/quic_stream_sequencer.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_STREAM_SEQUENCER_H_
+#define NET_QUIC_QUIC_STREAM_SEQUENCER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/base/iovec.h"
+#include "net/quic/quic_protocol.h"
+
+using std::map;
+using std::string;
+
+namespace net {
+
+namespace test {
+class QuicStreamSequencerPeer;
+}  // namespace test
+
+class QuicSession;
+class ReliableQuicStream;
+
+// Buffers frames until we have something which can be passed
+// up to the next layer.
+// TOOD(alyssar) add some checks for overflow attempts [1, 256,] [2, 256]
+class NET_EXPORT_PRIVATE QuicStreamSequencer {
+ public:
+  explicit QuicStreamSequencer(ReliableQuicStream* quic_stream);
+  virtual ~QuicStreamSequencer();
+
+  // If the frame is the next one we need in order to process in-order data,
+  // ProcessData will be immediately called on the stream until all buffered
+  // data is processed or the stream fails to consume data.  Any unconsumed
+  // data will be buffered. If the frame is not the next in line, it will be
+  // buffered.
+  void OnStreamFrame(const QuicStreamFrame& frame);
+
+  // Once data is buffered, it's up to the stream to read it when the stream
+  // can handle more data.  The following three functions make that possible.
+
+  // Fills in up to iov_len iovecs with the next readable regions.  Returns the
+  // number of iovs used.  Non-destructive of the underlying data.
+  int GetReadableRegions(iovec* iov, size_t iov_len);
+
+  // Copies the data into the iov_len buffers provided.  Returns the number of
+  // bytes read.  Any buffered data no longer in use will be released.
+  int Readv(const struct iovec* iov, size_t iov_len);
+
+  // Returns true if the sequncer has bytes available for reading.
+  bool HasBytesToRead() const;
+
+  // Returns true if the sequencer has delivered the fin.
+  bool IsClosed() const;
+
+  // Returns true if the sequencer has received this frame before.
+  bool IsDuplicate(const QuicStreamFrame& frame) const;
+
+  // Returns true if |frame| contains data which overlaps buffered data
+  // (indicating an invalid stream frame has been received).
+  bool FrameOverlapsBufferedData(const QuicStreamFrame& frame) const;
+
+  // Calls |ProcessRawData| on |stream_| for each buffered frame that may
+  // be processed.
+  void FlushBufferedFrames();
+
+  // Blocks processing of frames until |FlushBufferedFrames| is called.
+  void SetBlockedUntilFlush();
+
+  size_t num_bytes_buffered() const { return num_bytes_buffered_; }
+  QuicStreamOffset num_bytes_consumed() const { return num_bytes_consumed_; }
+
+  int num_frames_received() const { return num_frames_received_; }
+
+  int num_duplicate_frames_received() const {
+    return num_duplicate_frames_received_;
+  }
+
+ private:
+  friend class test::QuicStreamSequencerPeer;
+
+  // Wait until we've seen 'offset' bytes, and then terminate the stream.
+  void CloseStreamAtOffset(QuicStreamOffset offset);
+
+  // If we've received a FIN and have processed all remaining data, then inform
+  // the stream of FIN, and clear buffers.
+  bool MaybeCloseStream();
+
+  // Called whenever bytes are consumed by the stream. Updates
+  // num_bytes_consumed_ and num_bytes_buffered_.
+  void RecordBytesConsumed(size_t bytes_consumed);
+
+  // The stream which owns this sequencer.
+  ReliableQuicStream* stream_;
+
+  // The last data consumed by the stream.
+  QuicStreamOffset num_bytes_consumed_;
+
+  // TODO(alyssar) use something better than strings.
+  // TODO(rjshade): In future we may support retransmission of partial stream
+  // frames, in which case we will have to allow receipt of overlapping frames.
+  // Maybe write new frames into a ring buffer, and keep track of consumed
+  // bytes, and gaps.
+  typedef map<QuicStreamOffset, string> FrameMap;
+
+  // Stores buffered frames (maps from sequence number -> frame data as string).
+  FrameMap buffered_frames_;
+
+  // The offset, if any, we got a stream termination for.  When this many bytes
+  // have been processed, the sequencer will be closed.
+  QuicStreamOffset close_offset_;
+
+  // If true, the sequencer is blocked from passing data to the stream and will
+  // buffer all new incoming data until FlushBufferedFrames is called.
+  bool blocked_;
+
+  // Tracks how many bytes the sequencer has buffered.
+  size_t num_bytes_buffered_;
+
+  // Count of the number of frames received.
+  int num_frames_received_;
+
+  // Count of the number of duplicate frames received.
+  int num_duplicate_frames_received_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicStreamSequencer);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_STREAM_SEQUENCER_H_
diff --git a/net/quic/quic_stream_sequencer_test.cc b/net/quic/quic_stream_sequencer_test.cc
new file mode 100644
index 0000000..c553fe5
--- /dev/null
+++ b/net/quic/quic_stream_sequencer_test.cc
@@ -0,0 +1,445 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_stream_sequencer.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/quic/test_tools/quic_stream_sequencer_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::map;
+using std::min;
+using std::pair;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Return;
+using testing::StrEq;
+
+namespace net {
+namespace test {
+
+class MockStream : public ReliableQuicStream {
+ public:
+  MockStream(QuicSession* session, QuicStreamId id)
+      : ReliableQuicStream(id, session) {
+  }
+
+  MOCK_METHOD0(OnFinRead, void());
+  MOCK_METHOD2(ProcessRawData, uint32(const char* data, uint32 data_len));
+  MOCK_METHOD2(CloseConnectionWithDetails, void(QuicErrorCode error,
+                                                const string& details));
+  MOCK_METHOD1(Reset, void(QuicRstStreamErrorCode error));
+  MOCK_METHOD0(OnCanWrite, void());
+  virtual QuicPriority EffectivePriority() const OVERRIDE {
+    return QuicUtils::HighestPriority();
+  }
+  virtual bool IsFlowControlEnabled() const {
+    return true;
+  }
+};
+
+namespace {
+
+static const char kPayload[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+class QuicStreamSequencerTest : public ::testing::Test {
+ protected:
+  QuicStreamSequencerTest()
+      : connection_(new MockConnection(false)),
+        session_(connection_),
+        stream_(&session_, 1),
+        sequencer_(new QuicStreamSequencer(&stream_)),
+        buffered_frames_(
+            QuicStreamSequencerPeer::GetBufferedFrames(sequencer_.get())) {
+  }
+
+  bool VerifyReadableRegions(const char** expected, size_t num_expected) {
+    iovec iovecs[5];
+    size_t num_iovecs = sequencer_->GetReadableRegions(iovecs,
+                                                       arraysize(iovecs));
+    return VerifyIovecs(iovecs, num_iovecs, expected, num_expected);
+  }
+
+  bool VerifyIovecs(iovec* iovecs,
+                    size_t num_iovecs,
+                    const char** expected,
+                    size_t num_expected) {
+    if (num_expected != num_iovecs) {
+      LOG(ERROR) << "Incorrect number of iovecs.  Expected: "
+                 << num_expected << " Actual: " << num_iovecs;
+      return false;
+    }
+    for (size_t i = 0; i < num_expected; ++i) {
+      if (!VerifyIovec(iovecs[i], expected[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool VerifyIovec(const iovec& iovec, StringPiece expected) {
+    if (iovec.iov_len != expected.length()) {
+      LOG(ERROR) << "Invalid length: " << iovec.iov_len
+                 << " vs " << expected.length();
+      return false;
+    }
+    if (memcmp(iovec.iov_base, expected.data(), expected.length()) != 0) {
+      LOG(ERROR) << "Invalid data: " << static_cast<char*>(iovec.iov_base)
+                 << " vs " << expected.data();
+      return false;
+    }
+    return true;
+  }
+
+  void OnFinFrame(QuicStreamOffset byte_offset, const char* data) {
+    QuicStreamFrame frame;
+    frame.stream_id = 1;
+    frame.offset = byte_offset;
+    frame.data.Append(const_cast<char*>(data), strlen(data));
+    frame.fin = true;
+    sequencer_->OnStreamFrame(frame);
+  }
+
+  void OnFrame(QuicStreamOffset byte_offset, const char* data) {
+    QuicStreamFrame frame;
+    frame.stream_id = 1;
+    frame.offset = byte_offset;
+    frame.data.Append(const_cast<char*>(data), strlen(data));
+    frame.fin = false;
+    sequencer_->OnStreamFrame(frame);
+  }
+
+  MockConnection* connection_;
+  MockSession session_;
+  testing::StrictMock<MockStream> stream_;
+  scoped_ptr<QuicStreamSequencer> sequencer_;
+  map<QuicStreamOffset, string>* buffered_frames_;
+};
+
+TEST_F(QuicStreamSequencerTest, RejectOldFrame) {
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+  // Ignore this - it matches a past sequence number and we should not see it
+  // again.
+  OnFrame(0, "def");
+  EXPECT_EQ(0u, buffered_frames_->size());
+}
+
+TEST_F(QuicStreamSequencerTest, RejectBufferedFrame) {
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3));
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+  // Ignore this - it matches a buffered frame.
+  // Right now there's no checking that the payload is consistent.
+  OnFrame(0, "def");
+  EXPECT_EQ(1u, buffered_frames_->size());
+}
+
+TEST_F(QuicStreamSequencerTest, FullFrameConsumed) {
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, BlockedThenFullFrameConsumed) {
+  sequencer_->SetBlockedUntilFlush();
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  sequencer_->FlushBufferedFrames();
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("def"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, OnFinRead());
+  OnFinFrame(3, "def");
+}
+
+TEST_F(QuicStreamSequencerTest, BlockedThenFullFrameAndFinConsumed) {
+  sequencer_->SetBlockedUntilFlush();
+
+  OnFinFrame(0, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, OnFinRead());
+  sequencer_->FlushBufferedFrames();
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, EmptyFrame) {
+  EXPECT_CALL(stream_,
+              CloseConnectionWithDetails(QUIC_INVALID_STREAM_FRAME, _));
+  OnFrame(0, "");
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, EmptyFinFrame) {
+  EXPECT_CALL(stream_, OnFinRead());
+  OnFinFrame(0, "");
+  EXPECT_EQ(0u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, PartialFrameConsumed) {
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(2));
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(2u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ("c", buffered_frames_->find(2)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, NextxFrameNotConsumed) {
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(0));
+
+  OnFrame(0, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ("abc", buffered_frames_->find(0)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, FutureFrameNotProcessed) {
+  OnFrame(3, "abc");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ("abc", buffered_frames_->find(3)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, OutOfOrderFrameProcessed) {
+  // Buffer the first
+  OnFrame(6, "ghi");
+  EXPECT_EQ(1u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ(3u, sequencer_->num_bytes_buffered());
+  // Buffer the second
+  OnFrame(3, "def");
+  EXPECT_EQ(2u, buffered_frames_->size());
+  EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ(6u, sequencer_->num_bytes_buffered());
+
+  InSequence s;
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("def"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("ghi"), 3)).WillOnce(Return(3));
+
+  // Ack right away
+  OnFrame(0, "abc");
+  EXPECT_EQ(9u, sequencer_->num_bytes_consumed());
+  EXPECT_EQ(0u, sequencer_->num_bytes_buffered());
+
+  EXPECT_EQ(0u, buffered_frames_->size());
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfCloseOrdered) {
+  InSequence s;
+
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, OnFinRead());
+  OnFinFrame(0, "abc");
+
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfCloseUnorderedWithFlush) {
+  OnFinFrame(6, "");
+  EXPECT_EQ(6u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+  InSequence s;
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("def"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, OnFinRead());
+
+  OnFrame(3, "def");
+  OnFrame(0, "abc");
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfUnordered) {
+  OnFinFrame(3, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+  InSequence s;
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(3));
+  EXPECT_CALL(stream_, OnFinRead());
+
+  OnFrame(0, "abc");
+}
+
+TEST_F(QuicStreamSequencerTest, TerminateWithReadv) {
+  char buffer[3];
+
+  OnFinFrame(3, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+
+  EXPECT_FALSE(sequencer_->IsClosed());
+
+  EXPECT_CALL(stream_, ProcessRawData(StrEq("abc"), 3)).WillOnce(Return(0));
+  OnFrame(0, "abc");
+
+  iovec iov = {&buffer[0], 3};
+  int bytes_read = sequencer_->Readv(&iov, 1);
+  EXPECT_EQ(3, bytes_read);
+  EXPECT_TRUE(sequencer_->IsClosed());
+}
+
+TEST_F(QuicStreamSequencerTest, MutipleOffsets) {
+  OnFinFrame(3, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+
+  EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+  OnFinFrame(5, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+
+  EXPECT_CALL(stream_, Reset(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+  OnFinFrame(1, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+
+  OnFinFrame(3, "");
+  EXPECT_EQ(3u, QuicStreamSequencerPeer::GetCloseOffset(sequencer_.get()));
+}
+
+class QuicSequencerRandomTest : public QuicStreamSequencerTest {
+ public:
+  typedef pair<int, string> Frame;
+  typedef vector<Frame> FrameList;
+
+  void CreateFrames() {
+    int payload_size = arraysize(kPayload) - 1;
+    int remaining_payload = payload_size;
+    while (remaining_payload != 0) {
+      int size = min(OneToN(6), remaining_payload);
+      int index = payload_size - remaining_payload;
+      list_.push_back(make_pair(index, string(kPayload + index, size)));
+      remaining_payload -= size;
+    }
+  }
+
+  QuicSequencerRandomTest() {
+    CreateFrames();
+  }
+
+  int OneToN(int n) {
+    return base::RandInt(1, n);
+  }
+
+  int MaybeProcessMaybeBuffer(const char* data, uint32 len) {
+    int to_process = len;
+    if (base::RandUint64() % 2 != 0) {
+      to_process = base::RandInt(0, len);
+    }
+    output_.append(data, to_process);
+    return to_process;
+  }
+
+  string output_;
+  FrameList list_;
+};
+
+// All frames are processed as soon as we have sequential data.
+// Infinite buffering, so all frames are acked right away.
+TEST_F(QuicSequencerRandomTest, RandomFramesNoDroppingNoBackup) {
+  InSequence s;
+  for (size_t i = 0; i < list_.size(); ++i) {
+    string* data = &list_[i].second;
+    EXPECT_CALL(stream_, ProcessRawData(StrEq(*data), data->size()))
+        .WillOnce(Return(data->size()));
+  }
+
+  while (!list_.empty()) {
+    int index = OneToN(list_.size()) - 1;
+    LOG(ERROR) << "Sending index " << index << " " << list_[index].second;
+    OnFrame(list_[index].first, list_[index].second.data());
+
+    list_.erase(list_.begin() + index);
+  }
+}
+
+TEST_F(QuicStreamSequencerTest, FrameOverlapsBufferedData) {
+  // Ensure that FrameOverlapsBufferedData returns appropriate responses when
+  // there is existing data buffered.
+
+  map<QuicStreamOffset, string>* buffered_frames =
+      QuicStreamSequencerPeer::GetBufferedFrames(sequencer_.get());
+
+  const int kBufferedOffset = 10;
+  const int kBufferedDataLength = 3;
+  const int kNewDataLength = 3;
+  IOVector data = MakeIOVector(string(kNewDataLength, '.'));
+
+  // No overlap if no buffered frames.
+  EXPECT_TRUE(buffered_frames_->empty());
+  EXPECT_FALSE(sequencer_->FrameOverlapsBufferedData(
+      QuicStreamFrame(1, false, kBufferedOffset - 1, data)));
+
+  // Add a buffered frame.
+  buffered_frames->insert(
+      make_pair(kBufferedOffset, string(kBufferedDataLength, '.')));
+
+  // New byte range partially overlaps with buffered frame, start offset
+  // preceeding buffered frame.
+  EXPECT_TRUE(sequencer_->FrameOverlapsBufferedData(
+      QuicStreamFrame(1, false, kBufferedOffset - 1, data)));
+  EXPECT_TRUE(sequencer_->FrameOverlapsBufferedData(
+      QuicStreamFrame(1, false, kBufferedOffset - kNewDataLength + 1, data)));
+
+  // New byte range partially overlaps with buffered frame, start offset
+  // inside existing buffered frame.
+  EXPECT_TRUE(sequencer_->FrameOverlapsBufferedData(
+      QuicStreamFrame(1, false, kBufferedOffset + 1, data)));
+  EXPECT_TRUE(sequencer_->FrameOverlapsBufferedData(QuicStreamFrame(
+      1, false, kBufferedOffset + kBufferedDataLength - 1, data)));
+
+  // New byte range entirely outside of buffered frames, start offset preceeding
+  // buffered frame.
+  EXPECT_FALSE(sequencer_->FrameOverlapsBufferedData(
+      QuicStreamFrame(1, false, kBufferedOffset - kNewDataLength, data)));
+
+  // New byte range entirely outside of buffered frames, start offset later than
+  // buffered frame.
+  EXPECT_FALSE(sequencer_->FrameOverlapsBufferedData(QuicStreamFrame(
+      1, false, kBufferedOffset + kBufferedDataLength, data)));
+}
+
+TEST_F(QuicStreamSequencerTest, DontAcceptOverlappingFrames) {
+  // The peer should never send us non-identical stream frames which contain
+  // overlapping byte ranges - if they do, we close the connection.
+
+  QuicStreamFrame frame1(kClientDataStreamId1, false, 1, MakeIOVector("hello"));
+  sequencer_->OnStreamFrame(frame1);
+
+  QuicStreamFrame frame2(kClientDataStreamId1, false, 2, MakeIOVector("hello"));
+  EXPECT_TRUE(sequencer_->FrameOverlapsBufferedData(frame2));
+  EXPECT_CALL(stream_, CloseConnectionWithDetails(QUIC_INVALID_STREAM_FRAME, _))
+      .Times(1);
+  sequencer_->OnStreamFrame(frame2);
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_sustained_bandwidth_recorder.cc b/net/quic/quic_sustained_bandwidth_recorder.cc
new file mode 100644
index 0000000..41aa51e
--- /dev/null
+++ b/net/quic/quic_sustained_bandwidth_recorder.cc
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sustained_bandwidth_recorder.h"
+
+#include "base/logging.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+QuicSustainedBandwidthRecorder::QuicSustainedBandwidthRecorder()
+    : has_estimate_(false),
+      is_recording_(false),
+      bandwidth_estimate_recorded_during_slow_start_(false),
+      bandwidth_estimate_(QuicBandwidth::Zero()),
+      max_bandwidth_estimate_(QuicBandwidth::Zero()),
+      max_bandwidth_timestamp_(0),
+      start_time_(QuicTime::Zero()) {}
+
+void QuicSustainedBandwidthRecorder::RecordEstimate(bool in_recovery,
+                                                    bool in_slow_start,
+                                                    QuicBandwidth bandwidth,
+                                                    QuicTime estimate_time,
+                                                    QuicWallTime wall_time,
+                                                    QuicTime::Delta srtt) {
+  if (in_recovery) {
+    is_recording_ = false;
+    DVLOG(1) << "Stopped recording at: " << estimate_time.ToDebuggingValue();
+    return;
+  }
+
+  if (!is_recording_) {
+    // This is the first estimate of a new recording period.
+    start_time_ = estimate_time;
+    is_recording_ = true;
+    DVLOG(1) << "Started recording at: " << start_time_.ToDebuggingValue();
+    return;
+  }
+
+  // If we have been recording for at least 3 * srtt, then record the latest
+  // bandwidth estimate as a valid sustained bandwidth estimate.
+  if (estimate_time.Subtract(start_time_) >= srtt.Multiply(3)) {
+    has_estimate_ = true;
+    bandwidth_estimate_recorded_during_slow_start_ = in_slow_start;
+    bandwidth_estimate_ = bandwidth;
+    DVLOG(1) << "New sustained bandwidth estimate (KBytes/s): "
+             << bandwidth_estimate_.ToKBytesPerSecond();
+  }
+
+  // Check for an increase in max bandwidth.
+  if (bandwidth > max_bandwidth_estimate_) {
+    max_bandwidth_estimate_ = bandwidth;
+    max_bandwidth_timestamp_ = wall_time.ToUNIXSeconds();
+    DVLOG(1) << "New max bandwidth estimate (KBytes/s): "
+             << max_bandwidth_estimate_.ToKBytesPerSecond();
+  }
+}
+
+}  // namespace net
diff --git a/net/quic/quic_sustained_bandwidth_recorder.h b/net/quic/quic_sustained_bandwidth_recorder.h
new file mode 100644
index 0000000..11685d2
--- /dev/null
+++ b/net/quic/quic_sustained_bandwidth_recorder.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_SUSTAINED_BANDWIDTH_RECORDER_H_
+#define NET_QUIC_QUIC_SUSTAINED_BANDWIDTH_RECORDER_H_
+
+#include "base/logging.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+namespace test {
+class QuicSustainedBandwidthRecorderPeer;
+}  // namespace test
+
+// This class keeps track of a sustained bandwidth estimate to ultimately send
+// to the client in a server config update message. A sustained bandwidth
+// estimate is only marked as valid if the QuicSustainedBandwidthRecorder has
+// been given uninterrupted reliable estimates over a certain period of time.
+class NET_EXPORT_PRIVATE QuicSustainedBandwidthRecorder {
+ public:
+  QuicSustainedBandwidthRecorder();
+
+  // As long as |in_recovery| is consistently false, multiple calls to this
+  // method over a 3 * srtt period results in storage of a valid sustained
+  // bandwidth estimate.
+  // |time_now| is used as a max bandwidth timestamp if needed.
+  void RecordEstimate(bool in_recovery,
+                      bool in_slow_start,
+                      QuicBandwidth bandwidth,
+                      QuicTime estimate_time,
+                      QuicWallTime wall_time,
+                      QuicTime::Delta srtt);
+
+  bool HasEstimate() const {
+    return has_estimate_;
+  }
+
+  QuicBandwidth BandwidthEstimate() const {
+    DCHECK(has_estimate_);
+    return bandwidth_estimate_;
+  }
+
+  QuicBandwidth MaxBandwidthEstimate() const {
+    DCHECK(has_estimate_);
+    return max_bandwidth_estimate_;
+  }
+
+  int64 MaxBandwidthTimestamp() const {
+    DCHECK(has_estimate_);
+    return max_bandwidth_timestamp_;
+  }
+
+  bool EstimateRecordedDuringSlowStart() const {
+    DCHECK(has_estimate_);
+    return bandwidth_estimate_recorded_during_slow_start_;
+  }
+
+ private:
+  friend class test::QuicSustainedBandwidthRecorderPeer;
+
+  // True if we have been able to calculate sustained bandwidth, over at least
+  // one recording period (3 * rtt).
+  bool has_estimate_;
+
+  // True if the last call to RecordEstimate had a reliable estimate.
+  bool is_recording_;
+
+  // True if the current sustained bandwidth estimate was generated while in
+  // slow start.
+  bool bandwidth_estimate_recorded_during_slow_start_;
+
+  // The latest sustained bandwidth estimate.
+  QuicBandwidth bandwidth_estimate_;
+
+  // The maximum sustained bandwidth seen over the lifetime of the connection.
+  QuicBandwidth max_bandwidth_estimate_;
+
+  // Timestamp indicating when the max_bandwidth_estimate_ was seen.
+  int64 max_bandwidth_timestamp_;
+
+  // Timestamp marking the beginning of the latest recording period.
+  QuicTime start_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicSustainedBandwidthRecorder);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_SUSTAINED_BANDWIDTH_RECORDER_H_
diff --git a/net/quic/quic_sustained_bandwidth_recorder_test.cc b/net/quic/quic_sustained_bandwidth_recorder_test.cc
new file mode 100644
index 0000000..6a5fbb2
--- /dev/null
+++ b/net/quic/quic_sustained_bandwidth_recorder_test.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_sustained_bandwidth_recorder.h"
+
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicSustainedBandwidthRecorderTest, BandwidthEstimates) {
+  QuicSustainedBandwidthRecorder recorder;
+  EXPECT_FALSE(recorder.HasEstimate());
+
+  QuicTime estimate_time = QuicTime::Zero();
+  QuicWallTime wall_time = QuicWallTime::Zero();
+  QuicTime::Delta srtt = QuicTime::Delta::FromMilliseconds(150);
+  const int kBandwidthBitsPerSecond = 12345678;
+  QuicBandwidth bandwidth =
+      QuicBandwidth::FromBitsPerSecond(kBandwidthBitsPerSecond);
+
+  bool in_recovery = false;
+  bool in_slow_start = false;
+
+  // This triggers recording, but should not yield a valid estimate yet.
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_FALSE(recorder.HasEstimate());
+
+  // Send a second reading, again this should not result in a valid estimate,
+  // as not enough time has passed.
+  estimate_time = estimate_time.Add(srtt);
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_FALSE(recorder.HasEstimate());
+
+  // Now 3 * kSRTT has elapsed since first recording, expect a valid estimate.
+  estimate_time = estimate_time.Add(srtt);
+  estimate_time = estimate_time.Add(srtt);
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_TRUE(recorder.HasEstimate());
+  EXPECT_EQ(recorder.BandwidthEstimate(), bandwidth);
+  EXPECT_EQ(recorder.BandwidthEstimate(), recorder.MaxBandwidthEstimate());
+
+  // Resetting, and sending a different estimate will only change output after
+  // a further 3 * kSRTT has passed.
+  QuicBandwidth second_bandwidth =
+      QuicBandwidth::FromBitsPerSecond(2 * kBandwidthBitsPerSecond);
+  // Reset the recorder by passing in a measurement while in recovery.
+  in_recovery = true;
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  in_recovery = false;
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_EQ(recorder.BandwidthEstimate(), bandwidth);
+
+  estimate_time = estimate_time.Add(srtt.Multiply(3));
+  const int64 kSeconds = 556677;
+  QuicWallTime second_bandwidth_wall_time =
+      QuicWallTime::FromUNIXSeconds(kSeconds);
+  recorder.RecordEstimate(in_recovery, in_slow_start, second_bandwidth,
+                          estimate_time, second_bandwidth_wall_time, srtt);
+  EXPECT_EQ(recorder.BandwidthEstimate(), second_bandwidth);
+  EXPECT_EQ(recorder.BandwidthEstimate(), recorder.MaxBandwidthEstimate());
+  EXPECT_EQ(recorder.MaxBandwidthTimestamp(), kSeconds);
+
+  // Reset again, this time recording a lower bandwidth than before.
+  QuicBandwidth third_bandwidth =
+      QuicBandwidth::FromBitsPerSecond(0.5 * kBandwidthBitsPerSecond);
+  // Reset the recorder by passing in an unreliable measurement.
+  recorder.RecordEstimate(in_recovery, in_slow_start, third_bandwidth,
+                          estimate_time, wall_time, srtt);
+  recorder.RecordEstimate(in_recovery, in_slow_start, third_bandwidth,
+                          estimate_time, wall_time, srtt);
+  EXPECT_EQ(recorder.BandwidthEstimate(), third_bandwidth);
+
+  estimate_time = estimate_time.Add(srtt.Multiply(3));
+  recorder.RecordEstimate(in_recovery, in_slow_start, third_bandwidth,
+                          estimate_time, wall_time, srtt);
+  EXPECT_EQ(recorder.BandwidthEstimate(), third_bandwidth);
+
+  // Max bandwidth should not have changed.
+  EXPECT_LT(third_bandwidth, second_bandwidth);
+  EXPECT_EQ(recorder.MaxBandwidthEstimate(), second_bandwidth);
+  EXPECT_EQ(recorder.MaxBandwidthTimestamp(), kSeconds);
+}
+
+TEST(QuicSustainedBandwidthRecorderTest, SlowStart) {
+  // Verify that slow start status is correctly recorded.
+  QuicSustainedBandwidthRecorder recorder;
+  EXPECT_FALSE(recorder.HasEstimate());
+
+  QuicTime estimate_time = QuicTime::Zero();
+  QuicWallTime wall_time = QuicWallTime::Zero();
+  QuicTime::Delta srtt = QuicTime::Delta::FromMilliseconds(150);
+  const int kBandwidthBitsPerSecond = 12345678;
+  QuicBandwidth bandwidth =
+      QuicBandwidth::FromBitsPerSecond(kBandwidthBitsPerSecond);
+
+  bool in_recovery = false;
+  bool in_slow_start = true;
+
+  // This triggers recording, but should not yield a valid estimate yet.
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+
+  // Now 3 * kSRTT has elapsed since first recording, expect a valid estimate.
+  estimate_time = estimate_time.Add(srtt.Multiply(3));
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_TRUE(recorder.HasEstimate());
+  EXPECT_TRUE(recorder.EstimateRecordedDuringSlowStart());
+
+  // Now send another estimate, this time not in slow start.
+  estimate_time = estimate_time.Add(srtt.Multiply(3));
+  in_slow_start = false;
+  recorder.RecordEstimate(in_recovery, in_slow_start, bandwidth, estimate_time,
+                          wall_time, srtt);
+  EXPECT_TRUE(recorder.HasEstimate());
+  EXPECT_FALSE(recorder.EstimateRecordedDuringSlowStart());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_time.cc b/net/quic/quic_time.cc
new file mode 100644
index 0000000..e20a338
--- /dev/null
+++ b/net/quic/quic_time.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_time.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+// Highest number of microseconds that DateTimeOffset can hold.
+const int64 kQuicInfiniteTimeUs = GG_INT64_C(0x7fffffffffffffff) / 10;
+
+QuicTime::Delta::Delta(base::TimeDelta delta)
+    : delta_(delta) {
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::Zero() {
+  return QuicTime::Delta::FromMicroseconds(0);
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::Infinite() {
+  return QuicTime::Delta::FromMicroseconds(kQuicInfiniteTimeUs);
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromSeconds(int64 seconds) {
+  return QuicTime::Delta(base::TimeDelta::FromSeconds(seconds));
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromMilliseconds(int64 ms) {
+  return QuicTime::Delta(base::TimeDelta::FromMilliseconds(ms));
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromMicroseconds(int64 us) {
+  return QuicTime::Delta(base::TimeDelta::FromMicroseconds(us));
+}
+
+int64 QuicTime::Delta::ToSeconds() const {
+  return delta_.InSeconds();
+}
+
+int64 QuicTime::Delta::ToMilliseconds() const {
+  return delta_.InMilliseconds();
+}
+
+int64 QuicTime::Delta::ToMicroseconds() const {
+  return delta_.InMicroseconds();
+}
+
+QuicTime::Delta QuicTime::Delta::Add(const Delta& delta) const {
+  return QuicTime::Delta::FromMicroseconds(ToMicroseconds() +
+                                           delta.ToMicroseconds());
+}
+
+QuicTime::Delta QuicTime::Delta::Subtract(const Delta& delta) const {
+  return QuicTime::Delta::FromMicroseconds(ToMicroseconds() -
+                                           delta.ToMicroseconds());
+}
+
+QuicTime::Delta QuicTime::Delta::Multiply(int i) const {
+  return QuicTime::Delta::FromMicroseconds(ToMicroseconds() * i);
+}
+
+QuicTime::Delta QuicTime::Delta::Multiply(double d) const {
+  return QuicTime::Delta::FromMicroseconds(ToMicroseconds() * d);
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::Max(QuicTime::Delta delta1,
+                                     QuicTime::Delta delta2) {
+  return delta1 < delta2 ? delta2 : delta1;
+}
+
+bool QuicTime::Delta::IsZero() const {
+  return delta_.InMicroseconds() == 0;
+}
+
+bool QuicTime::Delta::IsInfinite() const {
+  return delta_.InMicroseconds() == kQuicInfiniteTimeUs;
+}
+
+// static
+QuicTime QuicTime::Zero() {
+  return QuicTime(base::TimeTicks());
+}
+
+// static
+QuicTime QuicTime::Infinite() {
+  return QuicTime(base::TimeTicks::FromInternalValue(kQuicInfiniteTimeUs));
+}
+
+// static
+QuicTime QuicTime::Max(QuicTime time1, QuicTime time2) {
+  return time1 > time2 ? time1 : time2;
+}
+
+QuicTime::QuicTime(base::TimeTicks ticks)
+    : ticks_(ticks) {
+}
+
+int64 QuicTime::ToDebuggingValue() const {
+  return (ticks_ - base::TimeTicks()).InMicroseconds();
+}
+
+bool QuicTime::IsInitialized() const {
+  return ticks_ != base::TimeTicks();
+}
+
+QuicTime QuicTime::Add(const Delta& delta) const {
+  return QuicTime(ticks_ + delta.delta_);
+}
+
+QuicTime QuicTime::Subtract(const Delta& delta) const {
+  return QuicTime(ticks_ - delta.delta_);
+}
+
+QuicTime::Delta QuicTime::Subtract(const QuicTime& other) const {
+  return QuicTime::Delta(ticks_ - other.ticks_);
+}
+
+// static
+QuicWallTime QuicWallTime::FromUNIXSeconds(uint64 seconds) {
+  return QuicWallTime(seconds);
+}
+
+// static
+QuicWallTime QuicWallTime::Zero() {
+  return QuicWallTime(0);
+}
+
+uint64 QuicWallTime::ToUNIXSeconds() const {
+  return seconds_;
+}
+
+bool QuicWallTime::IsAfter(QuicWallTime other) const {
+  return seconds_ > other.seconds_;
+}
+
+bool QuicWallTime::IsBefore(QuicWallTime other) const {
+  return seconds_ < other.seconds_;
+}
+
+bool QuicWallTime::IsZero() const {
+  return seconds_ == 0;
+}
+
+QuicTime::Delta QuicWallTime::AbsoluteDifference(QuicWallTime other) const {
+  uint64 d;
+
+  if (seconds_ > other.seconds_) {
+    d = seconds_ - other.seconds_;
+  } else {
+    d = other.seconds_ - seconds_;
+  }
+
+  if (d > static_cast<uint64>(kint64max)) {
+    d = kint64max;
+  }
+  return QuicTime::Delta::FromSeconds(d);
+}
+
+QuicWallTime QuicWallTime::Add(QuicTime::Delta delta) const {
+  uint64 seconds = seconds_ + delta.ToSeconds();
+  if (seconds < seconds_) {
+    seconds = kuint64max;
+  }
+  return QuicWallTime(seconds);
+}
+
+QuicWallTime QuicWallTime::Subtract(QuicTime::Delta delta) const {
+  uint64 seconds = seconds_ - delta.ToSeconds();
+  if (seconds > seconds_) {
+    seconds = 0;
+  }
+  return QuicWallTime(seconds);
+}
+
+QuicWallTime::QuicWallTime(uint64 seconds)
+    : seconds_(seconds) {
+}
+
+}  // namespace net
diff --git a/net/quic/quic_time.h b/net/quic/quic_time.h
new file mode 100644
index 0000000..5fbe30c
--- /dev/null
+++ b/net/quic/quic_time.h
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// QuicTime represents one point in time, stored in microsecond resolution.
+// QuicTime is monotonically increasing, even across system clock adjustments.
+// The epoch (time 0) of QuicTime is unspecified.
+//
+// This implementation wraps the classes base::TimeTicks and base::TimeDelta.
+
+#ifndef NET_QUIC_QUIC_TIME_H_
+#define NET_QUIC_QUIC_TIME_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+static const uint64 kNumMicrosPerSecond = base::Time::kMicrosecondsPerSecond;
+
+// A QuicTime is a purely relative time. QuicTime values from different clocks
+// cannot be compared to each other. If you need an absolute time, see
+// QuicWallTime, below.
+class NET_EXPORT_PRIVATE QuicTime {
+ public:
+  // A QuicTime::Delta represents the signed difference between two points in
+  // time, stored in microsecond resolution.
+  class NET_EXPORT_PRIVATE Delta {
+   public:
+    explicit Delta(base::TimeDelta delta);
+
+    // Create a object with an offset of 0.
+    static Delta Zero();
+
+    // Create a object with infinite offset time.
+    static Delta Infinite();
+
+    // Converts a number of seconds to a time offset.
+    static Delta FromSeconds(int64 secs);
+
+    // Converts a number of milliseconds to a time offset.
+    static Delta FromMilliseconds(int64 ms);
+
+    // Converts a number of microseconds to a time offset.
+    static Delta FromMicroseconds(int64 us);
+
+    // Converts the time offset to a rounded number of seconds.
+    int64 ToSeconds() const;
+
+    // Converts the time offset to a rounded number of milliseconds.
+    int64 ToMilliseconds() const;
+
+    // Converts the time offset to a rounded number of microseconds.
+    int64 ToMicroseconds() const;
+
+    Delta Add(const Delta& delta) const;
+
+    Delta Subtract(const Delta& delta) const;
+
+    Delta Multiply(int i) const;
+    Delta Multiply(double d) const;
+
+    // Returns the later delta of time1 and time2.
+    static Delta Max(Delta delta1, Delta delta2);
+
+    bool IsZero() const;
+
+    bool IsInfinite() const;
+
+   private:
+    base::TimeDelta delta_;
+
+    friend class QuicTime;
+    friend class QuicClock;
+  };
+
+  explicit QuicTime(base::TimeTicks ticks);
+
+  // Creates a new QuicTime with an internal value of 0.  IsInitialized()
+  // will return false for these times.
+  static QuicTime Zero();
+
+  // Creates a new QuicTime with an infinite time.
+  static QuicTime Infinite();
+
+  // Returns the later time of time1 and time2.
+  static QuicTime Max(QuicTime time1, QuicTime time2);
+
+  // Produce the internal value to be used when logging.  This value
+  // represents the number of microseconds since some epoch.  It may
+  // be the UNIX epoch on some platforms.  On others, it may
+  // be a CPU ticks based value.
+  int64 ToDebuggingValue() const;
+
+  bool IsInitialized() const;
+
+  QuicTime Add(const Delta& delta) const;
+
+  QuicTime Subtract(const Delta& delta) const;
+
+  Delta Subtract(const QuicTime& other) const;
+
+ private:
+  friend bool operator==(QuicTime lhs, QuicTime rhs);
+  friend bool operator<(QuicTime lhs, QuicTime rhs);
+
+  friend class QuicClock;
+  friend class QuicClockTest;
+
+  base::TimeTicks ticks_;
+};
+
+// A QuicWallTime represents an absolute time that is globally consistent. It
+// provides, at most, one second granularity and, in practice, clock-skew means
+// that you shouldn't even depend on that.
+class NET_EXPORT_PRIVATE QuicWallTime {
+ public:
+  // FromUNIXSeconds constructs a QuicWallTime from a count of the seconds
+  // since the UNIX epoch.
+  static QuicWallTime FromUNIXSeconds(uint64 seconds);
+
+  // Zero returns a QuicWallTime set to zero. IsZero will return true for this
+  // value.
+  static QuicWallTime Zero();
+
+  // ToUNIXSeconds converts a QuicWallTime into a count of seconds since the
+  // UNIX epoch.
+  uint64 ToUNIXSeconds() const;
+
+  bool IsAfter(QuicWallTime other) const;
+  bool IsBefore(QuicWallTime other) const;
+
+  // IsZero returns true if this object is the result of calling |Zero|.
+  bool IsZero() const;
+
+  // AbsoluteDifference returns the absolute value of the time difference
+  // between |this| and |other|.
+  QuicTime::Delta AbsoluteDifference(QuicWallTime other) const;
+
+  // Add returns a new QuicWallTime that represents the time of |this| plus
+  // |delta|.
+  QuicWallTime Add(QuicTime::Delta delta) const;
+
+  // Subtract returns a new QuicWallTime that represents the time of |this|
+  // minus |delta|.
+  QuicWallTime Subtract(QuicTime::Delta delta) const;
+
+ private:
+  explicit QuicWallTime(uint64 seconds);
+
+  uint64 seconds_;
+};
+
+// Non-member relational operators for QuicTime::Delta.
+inline bool operator==(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return lhs.ToMicroseconds() == rhs.ToMicroseconds();
+}
+inline bool operator!=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return !(lhs == rhs);
+}
+inline bool operator<(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return lhs.ToMicroseconds() < rhs.ToMicroseconds();
+}
+inline bool operator>(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return rhs < lhs;
+}
+inline bool operator<=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return !(rhs < lhs);
+}
+inline bool operator>=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+  return !(lhs < rhs);
+}
+// Non-member relational operators for QuicTime.
+inline bool operator==(QuicTime lhs, QuicTime rhs) {
+  return lhs.ticks_ == rhs.ticks_;
+}
+inline bool operator!=(QuicTime lhs, QuicTime rhs) {
+  return !(lhs == rhs);
+}
+inline bool operator<(QuicTime lhs, QuicTime rhs) {
+  return lhs.ticks_ < rhs.ticks_;
+}
+inline bool operator>(QuicTime lhs, QuicTime rhs) {
+  return rhs < lhs;
+}
+inline bool operator<=(QuicTime lhs, QuicTime rhs) {
+  return !(rhs < lhs);
+}
+inline bool operator>=(QuicTime lhs, QuicTime rhs) {
+  return !(lhs < rhs);
+}
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_TIME_H_
diff --git a/net/quic/quic_time_test.cc b/net/quic/quic_time_test.cc
new file mode 100644
index 0000000..a2e3550
--- /dev/null
+++ b/net/quic/quic_time_test.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_time.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicTimeDeltaTest, Zero) {
+  EXPECT_TRUE(QuicTime::Delta::Zero().IsZero());
+  EXPECT_FALSE(QuicTime::Delta::Zero().IsInfinite());
+  EXPECT_FALSE(QuicTime::Delta::FromMilliseconds(1).IsZero());
+}
+
+TEST(QuicTimeDeltaTest, Infinite) {
+  EXPECT_TRUE(QuicTime::Delta::Infinite().IsInfinite());
+  EXPECT_FALSE(QuicTime::Delta::Zero().IsInfinite());
+  EXPECT_FALSE(QuicTime::Delta::FromMilliseconds(1).IsInfinite());
+}
+
+TEST(QuicTimeDeltaTest, FromTo) {
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1),
+            QuicTime::Delta::FromMicroseconds(1000));
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+            QuicTime::Delta::FromMilliseconds(1000));
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+            QuicTime::Delta::FromMicroseconds(1000000));
+
+  EXPECT_EQ(1, QuicTime::Delta::FromMicroseconds(1000).ToMilliseconds());
+  EXPECT_EQ(2, QuicTime::Delta::FromMilliseconds(2000).ToSeconds());
+  EXPECT_EQ(1000, QuicTime::Delta::FromMilliseconds(1).ToMicroseconds());
+  EXPECT_EQ(1, QuicTime::Delta::FromMicroseconds(1000).ToMilliseconds());
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(2000).ToMicroseconds(),
+            QuicTime::Delta::FromSeconds(2).ToMicroseconds());
+}
+
+TEST(QuicTimeDeltaTest, Add) {
+  EXPECT_EQ(QuicTime::Delta::FromMicroseconds(2000),
+            QuicTime::Delta::Zero().Add(QuicTime::Delta::FromMilliseconds(2)));
+}
+
+TEST(QuicTimeDeltaTest, Subtract) {
+  EXPECT_EQ(QuicTime::Delta::FromMicroseconds(1000),
+            QuicTime::Delta::FromMilliseconds(2).Subtract(
+                QuicTime::Delta::FromMilliseconds(1)));
+}
+
+TEST(QuicTimeDeltaTest, Multiply) {
+  int i = 2;
+  EXPECT_EQ(QuicTime::Delta::FromMicroseconds(4000),
+            QuicTime::Delta::FromMilliseconds(2).Multiply(i));
+  double d = 2;
+  EXPECT_EQ(QuicTime::Delta::FromMicroseconds(4000),
+            QuicTime::Delta::FromMilliseconds(2).Multiply(d));
+}
+
+TEST(QuicTimeDeltaTest, Max) {
+  EXPECT_EQ(QuicTime::Delta::FromMicroseconds(2000),
+            QuicTime::Delta::Max(QuicTime::Delta::FromMicroseconds(1000),
+                                 QuicTime::Delta::FromMicroseconds(2000)));
+}
+
+TEST(QuicTimeDeltaTest, NotEqual) {
+  EXPECT_TRUE(QuicTime::Delta::FromSeconds(0) !=
+              QuicTime::Delta::FromSeconds(1));
+  EXPECT_FALSE(QuicTime::Delta::FromSeconds(0) !=
+               QuicTime::Delta::FromSeconds(0));
+}
+
+class QuicTimeTest : public ::testing::Test {
+ protected:
+  MockClock clock_;
+};
+
+TEST_F(QuicTimeTest, Initialized) {
+  EXPECT_FALSE(QuicTime::Zero().IsInitialized());
+  EXPECT_TRUE(QuicTime::Zero().Add(
+      QuicTime::Delta::FromMicroseconds(1)).IsInitialized());
+}
+
+TEST_F(QuicTimeTest, Add) {
+  QuicTime time_1 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(1));
+  QuicTime time_2 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(2));
+
+  QuicTime::Delta diff = time_2.Subtract(time_1);
+
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), diff);
+  EXPECT_EQ(1000, diff.ToMicroseconds());
+  EXPECT_EQ(1, diff.ToMilliseconds());
+}
+
+TEST_F(QuicTimeTest, Subtract) {
+  QuicTime time_1 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(1));
+  QuicTime time_2 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(2));
+
+  EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), time_2.Subtract(time_1));
+}
+
+TEST_F(QuicTimeTest, SubtractDelta) {
+  QuicTime time = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(2));
+  EXPECT_EQ(QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(1)),
+            time.Subtract(QuicTime::Delta::FromMilliseconds(1)));
+}
+
+TEST_F(QuicTimeTest, Max) {
+  QuicTime time_1 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(1));
+  QuicTime time_2 = QuicTime::Zero().Add(
+      QuicTime::Delta::FromMilliseconds(2));
+
+  EXPECT_EQ(time_2, QuicTime::Max(time_1, time_2));
+}
+
+TEST_F(QuicTimeTest, MockClock) {
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+
+  QuicTime now = clock_.ApproximateNow();
+  QuicTime time = QuicTime::Zero().Add(QuicTime::Delta::FromMicroseconds(1000));
+
+  EXPECT_EQ(now, time);
+
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+  now = clock_.ApproximateNow();
+
+  EXPECT_NE(now, time);
+
+  time = time.Add(QuicTime::Delta::FromMilliseconds(1));
+  EXPECT_EQ(now, time);
+}
+
+TEST_F(QuicTimeTest, LE) {
+  const QuicTime zero = QuicTime::Zero();
+  const QuicTime one  = zero.Add(QuicTime::Delta::FromSeconds(1));
+  EXPECT_TRUE(zero <= zero);
+  EXPECT_TRUE(zero <= one);
+  EXPECT_TRUE(one <= one);
+  EXPECT_FALSE(one <= zero);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_time_wait_list_manager.cc b/net/quic/quic_time_wait_list_manager.cc
new file mode 100644
index 0000000..d1f3419
--- /dev/null
+++ b/net/quic/quic_time_wait_list_manager.cc
@@ -0,0 +1,283 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_time_wait_list_manager.h"
+
+#include <errno.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_server_session.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::make_pair;
+
+namespace net {
+
+namespace {
+
+// Time period for which the connection_id should live in time wait state..
+const int kTimeWaitSeconds = 5;
+
+}  // namespace
+
+// A very simple alarm that just informs the QuicTimeWaitListManager to clean
+// up old connection_ids. This alarm should be unregistered and deleted before
+// the QuicTimeWaitListManager is deleted.
+class ConnectionIdCleanUpAlarm : public QuicAlarm::Delegate {
+ public:
+  explicit ConnectionIdCleanUpAlarm(
+      QuicTimeWaitListManager* time_wait_list_manager)
+      : time_wait_list_manager_(time_wait_list_manager) {}
+
+  virtual QuicTime OnAlarm() OVERRIDE {
+    time_wait_list_manager_->CleanUpOldConnectionIds();
+    // Let the time wait manager register the alarm at appropriate time.
+    return QuicTime::Zero();
+  }
+
+ private:
+  // Not owned.
+  QuicTimeWaitListManager* time_wait_list_manager_;
+};
+
+// This class stores pending public reset packets to be sent to clients.
+// server_address - server address on which a packet what was received for
+//                  a connection_id in time wait state.
+// client_address - address of the client that sent that packet. Needed to send
+//                  the public reset packet back to the client.
+// packet - the pending public reset packet that is to be sent to the client.
+//          created instance takes the ownership of this packet.
+class QuicTimeWaitListManager::QueuedPacket {
+ public:
+  QueuedPacket(const IPEndPoint& server_address,
+               const IPEndPoint& client_address,
+               QuicEncryptedPacket* packet)
+      : server_address_(server_address),
+        client_address_(client_address),
+        packet_(packet) {}
+
+  const IPEndPoint& server_address() const { return server_address_; }
+  const IPEndPoint& client_address() const { return client_address_; }
+  QuicEncryptedPacket* packet() { return packet_.get(); }
+
+ private:
+  const IPEndPoint server_address_;
+  const IPEndPoint client_address_;
+  scoped_ptr<QuicEncryptedPacket> packet_;
+
+  DISALLOW_COPY_AND_ASSIGN(QueuedPacket);
+};
+
+QuicTimeWaitListManager::QuicTimeWaitListManager(
+    QuicPacketWriter* writer,
+    QuicServerSessionVisitor* visitor,
+    QuicConnectionHelperInterface* helper,
+    const QuicVersionVector& supported_versions)
+    : helper_(helper),
+      kTimeWaitPeriod_(QuicTime::Delta::FromSeconds(kTimeWaitSeconds)),
+      connection_id_clean_up_alarm_(
+          helper_->CreateAlarm(new ConnectionIdCleanUpAlarm(this))),
+      writer_(writer),
+      visitor_(visitor) {
+  SetConnectionIdCleanUpAlarm();
+}
+
+QuicTimeWaitListManager::~QuicTimeWaitListManager() {
+  connection_id_clean_up_alarm_->Cancel();
+  STLDeleteElements(&pending_packets_queue_);
+  for (ConnectionIdMap::iterator it = connection_id_map_.begin();
+       it != connection_id_map_.end();
+       ++it) {
+    delete it->second.close_packet;
+  }
+}
+
+void QuicTimeWaitListManager::AddConnectionIdToTimeWait(
+    QuicConnectionId connection_id,
+    QuicVersion version,
+    QuicEncryptedPacket* close_packet) {
+  DVLOG(1) << "Adding " << connection_id << " to the time wait list.";
+  int num_packets = 0;
+  ConnectionIdMap::iterator it = connection_id_map_.find(connection_id);
+  if (it != connection_id_map_.end()) {  // Replace record if it is reinserted.
+    num_packets = it->second.num_packets;
+    delete it->second.close_packet;
+    connection_id_map_.erase(it);
+  }
+  ConnectionIdData data(num_packets,
+                        version,
+                        helper_->GetClock()->ApproximateNow(),
+                        close_packet);
+  connection_id_map_.insert(make_pair(connection_id, data));
+}
+
+bool QuicTimeWaitListManager::IsConnectionIdInTimeWait(
+    QuicConnectionId connection_id) const {
+  return ContainsKey(connection_id_map_, connection_id);
+}
+
+QuicVersion QuicTimeWaitListManager::GetQuicVersionFromConnectionId(
+    QuicConnectionId connection_id) {
+  ConnectionIdMap::iterator it = connection_id_map_.find(connection_id);
+  DCHECK(it != connection_id_map_.end());
+  return (it->second).version;
+}
+
+void QuicTimeWaitListManager::OnCanWrite() {
+  while (!pending_packets_queue_.empty()) {
+    QueuedPacket* queued_packet = pending_packets_queue_.front();
+    if (!WriteToWire(queued_packet)) {
+      return;
+    }
+    pending_packets_queue_.pop_front();
+    delete queued_packet;
+  }
+}
+
+void QuicTimeWaitListManager::ProcessPacket(
+    const IPEndPoint& server_address,
+    const IPEndPoint& client_address,
+    QuicConnectionId connection_id,
+    QuicPacketSequenceNumber sequence_number,
+    const QuicEncryptedPacket& /*packet*/) {
+  DCHECK(IsConnectionIdInTimeWait(connection_id));
+  DVLOG(1) << "Processing " << connection_id << " in time wait state.";
+  // TODO(satyamshekhar): Think about handling packets from different client
+  // addresses.
+  ConnectionIdMap::iterator it = connection_id_map_.find(connection_id);
+  DCHECK(it != connection_id_map_.end());
+  // Increment the received packet count.
+  ++((it->second).num_packets);
+  if (!ShouldSendResponse((it->second).num_packets)) {
+    return;
+  }
+  if (it->second.close_packet) {
+    QueuedPacket* queued_packet =
+        new QueuedPacket(server_address,
+                         client_address,
+                         it->second.close_packet->Clone());
+    // Takes ownership of the packet.
+    SendOrQueuePacket(queued_packet);
+  } else {
+    SendPublicReset(server_address,
+                    client_address,
+                    connection_id,
+                    sequence_number);
+  }
+}
+
+// Returns true if the number of packets received for this connection_id is a
+// power of 2 to throttle the number of public reset packets we send to a
+// client.
+bool QuicTimeWaitListManager::ShouldSendResponse(int received_packet_count) {
+  return (received_packet_count & (received_packet_count - 1)) == 0;
+}
+
+void QuicTimeWaitListManager::SendPublicReset(
+    const IPEndPoint& server_address,
+    const IPEndPoint& client_address,
+    QuicConnectionId connection_id,
+    QuicPacketSequenceNumber rejected_sequence_number) {
+  QuicPublicResetPacket packet;
+  packet.public_header.connection_id = connection_id;
+  packet.public_header.reset_flag = true;
+  packet.public_header.version_flag = false;
+  packet.rejected_sequence_number = rejected_sequence_number;
+  // TODO(satyamshekhar): generate a valid nonce for this connection_id.
+  packet.nonce_proof = 1010101;
+  packet.client_address = client_address;
+  QueuedPacket* queued_packet = new QueuedPacket(
+      server_address,
+      client_address,
+      BuildPublicReset(packet));
+  // Takes ownership of the packet.
+  SendOrQueuePacket(queued_packet);
+}
+
+QuicEncryptedPacket* QuicTimeWaitListManager::BuildPublicReset(
+    const QuicPublicResetPacket& packet) {
+  return QuicFramer::BuildPublicResetPacket(packet);
+}
+
+// Either sends the packet and deletes it or makes pending queue the
+// owner of the packet.
+void QuicTimeWaitListManager::SendOrQueuePacket(QueuedPacket* packet) {
+  if (WriteToWire(packet)) {
+    delete packet;
+  } else {
+    // pending_packets_queue takes the ownership of the queued packet.
+    pending_packets_queue_.push_back(packet);
+  }
+}
+
+bool QuicTimeWaitListManager::WriteToWire(QueuedPacket* queued_packet) {
+  if (writer_->IsWriteBlocked()) {
+    visitor_->OnWriteBlocked(this);
+    return false;
+  }
+  WriteResult result = writer_->WritePacket(
+      queued_packet->packet()->data(),
+      queued_packet->packet()->length(),
+      queued_packet->server_address().address(),
+      queued_packet->client_address());
+  if (result.status == WRITE_STATUS_BLOCKED) {
+    // If blocked and unbuffered, return false to retry sending.
+    DCHECK(writer_->IsWriteBlocked());
+    visitor_->OnWriteBlocked(this);
+    return writer_->IsWriteBlockedDataBuffered();
+  } else if (result.status == WRITE_STATUS_ERROR) {
+    LOG(WARNING) << "Received unknown error while sending reset packet to "
+                 << queued_packet->client_address().ToString() << ": "
+                 << strerror(result.error_code);
+  }
+  return true;
+}
+
+void QuicTimeWaitListManager::SetConnectionIdCleanUpAlarm() {
+  connection_id_clean_up_alarm_->Cancel();
+  QuicTime now = helper_->GetClock()->ApproximateNow();
+  QuicTime next_alarm_time = now;
+  if (!connection_id_map_.empty()) {
+    QuicTime oldest_connection_id =
+        connection_id_map_.begin()->second.time_added;
+    if (now.Subtract(oldest_connection_id) < kTimeWaitPeriod_) {
+      next_alarm_time = oldest_connection_id.Add(kTimeWaitPeriod_);
+    } else {
+      LOG(ERROR) << "ConnectionId lingered for longer than kTimeWaitPeriod";
+    }
+  } else {
+    // No connection_ids added so none will expire before kTimeWaitPeriod_.
+    next_alarm_time = now.Add(kTimeWaitPeriod_);
+  }
+
+  connection_id_clean_up_alarm_->Set(next_alarm_time);
+}
+
+void QuicTimeWaitListManager::CleanUpOldConnectionIds() {
+  QuicTime now = helper_->GetClock()->ApproximateNow();
+  while (!connection_id_map_.empty()) {
+    ConnectionIdMap::iterator it = connection_id_map_.begin();
+    QuicTime oldest_connection_id = it->second.time_added;
+    if (now.Subtract(oldest_connection_id) < kTimeWaitPeriod_) {
+      break;
+    }
+    // This connection_id has lived its age, retire it now.
+    delete it->second.close_packet;
+    connection_id_map_.erase(it);
+  }
+  SetConnectionIdCleanUpAlarm();
+}
+
+}  // namespace net
diff --git a/net/quic/quic_time_wait_list_manager.h b/net/quic/quic_time_wait_list_manager.h
new file mode 100644
index 0000000..4d7b0c5
--- /dev/null
+++ b/net/quic/quic_time_wait_list_manager.h
@@ -0,0 +1,174 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Handles packets for connection_ids in time wait state by discarding the
+// packet and sending the clients a public reset packet with exponential
+// backoff.
+
+#ifndef NET_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
+#define NET_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
+
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/strings/string_piece.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_writer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ConnectionIdCleanUpAlarm;
+class QuicServerSessionVisitor;
+
+namespace test {
+class QuicTimeWaitListManagerPeer;
+}  // namespace test
+
+// Maintains a list of all connection_ids that have been recently closed. A
+// connection_id lives in this state for kTimeWaitPeriod. All packets received
+// for connection_ids in this state are handed over to the
+// QuicTimeWaitListManager by the QuicDispatcher.  Decides whether to send a
+// public reset packet, a copy of the previously sent connection close packet,
+// or nothing to the client which sent a packet with the connection_id in time
+// wait state.  After the connection_id expires its time wait period, a new
+// connection/session will be created if a packet is received for this
+// connection_id.
+class QuicTimeWaitListManager : public QuicBlockedWriterInterface {
+ public:
+  // writer - the entity that writes to the socket. (Owned by the dispatcher)
+  // visitor - the entity that manages blocked writers. (The dispatcher)
+  // helper - used to run clean up alarms. (Owned by the owner of the server)
+  QuicTimeWaitListManager(QuicPacketWriter* writer,
+                          QuicServerSessionVisitor* visitor,
+                          QuicConnectionHelperInterface* helper,
+                          const QuicVersionVector& supported_versions);
+  virtual ~QuicTimeWaitListManager();
+
+  // Adds the given connection_id to time wait state for kTimeWaitPeriod.
+  // Henceforth, any packet bearing this connection_id should not be processed
+  // while the connection_id remains in this list. If a non-nullptr
+  // |close_packet| is provided, it is sent again when packets are received for
+  // added connection_ids. If nullptr, a public reset packet is sent with the
+  // specified |version|. DCHECKs that connection_id is not already on the list.
+  void AddConnectionIdToTimeWait(QuicConnectionId connection_id,
+                                 QuicVersion version,
+                                 QuicEncryptedPacket* close_packet);  // Owned.
+
+  // Returns true if the connection_id is in time wait state, false otherwise.
+  // Packets received for this connection_id should not lead to creation of new
+  // QuicSessions.
+  bool IsConnectionIdInTimeWait(QuicConnectionId connection_id) const;
+
+  // Called when a packet is received for a connection_id that is in time wait
+  // state. Sends a public reset packet to the client which sent this
+  // connection_id. Sending of the public reset packet is throttled by using
+  // exponential back off. DCHECKs for the connection_id to be in time wait
+  // state. virtual to override in tests.
+  virtual void ProcessPacket(const IPEndPoint& server_address,
+                             const IPEndPoint& client_address,
+                             QuicConnectionId connection_id,
+                             QuicPacketSequenceNumber sequence_number,
+                             const QuicEncryptedPacket& packet);
+
+  // Called by the dispatcher when the underlying socket becomes writable again,
+  // since we might need to send pending public reset packets which we didn't
+  // send because the underlying socket was write blocked.
+  virtual void OnCanWrite() OVERRIDE;
+
+  // Used to delete connection_id entries that have outlived their time wait
+  // period.
+  void CleanUpOldConnectionIds();
+
+  // Given a ConnectionId that exists in the time wait list, returns the
+  // QuicVersion associated with it.
+  QuicVersion GetQuicVersionFromConnectionId(QuicConnectionId connection_id);
+
+ protected:
+  virtual QuicEncryptedPacket* BuildPublicReset(
+      const QuicPublicResetPacket& packet);
+
+ private:
+  friend class test::QuicTimeWaitListManagerPeer;
+
+  // Internal structure to store pending public reset packets.
+  class QueuedPacket;
+
+  // Decides if a packet should be sent for this connection_id based on the
+  // number of received packets.
+  bool ShouldSendResponse(int received_packet_count);
+
+  // Creates a public reset packet and sends it or queues it to be sent later.
+  void SendPublicReset(const IPEndPoint& server_address,
+                       const IPEndPoint& client_address,
+                       QuicConnectionId connection_id,
+                       QuicPacketSequenceNumber rejected_sequence_number);
+
+  // Either sends the packet and deletes it or makes pending_packets_queue_ the
+  // owner of the packet.
+  void SendOrQueuePacket(QueuedPacket* packet);
+
+  // Sends the packet out. Returns true if the packet was successfully consumed.
+  // If the writer got blocked and did not buffer the packet, we'll need to keep
+  // the packet and retry sending. In case of all other errors we drop the
+  // packet.
+  bool WriteToWire(QueuedPacket* packet);
+
+  // Register the alarm to wake up at appropriate time.
+  void SetConnectionIdCleanUpAlarm();
+
+  // A map from a recently closed connection_id to the number of packets
+  // received after the termination of the connection bound to the
+  // connection_id.
+  struct ConnectionIdData {
+    ConnectionIdData(int num_packets_,
+                     QuicVersion version_,
+                     QuicTime time_added_,
+                     QuicEncryptedPacket* close_packet)
+        : num_packets(num_packets_),
+          version(version_),
+          time_added(time_added_),
+          close_packet(close_packet) {}
+    int num_packets;
+    QuicVersion version;
+    QuicTime time_added;
+    QuicEncryptedPacket* close_packet;
+  };
+
+  // linked_hash_map allows lookup by ConnectionId and traversal in add order.
+  typedef linked_hash_map<QuicConnectionId, ConnectionIdData> ConnectionIdMap;
+  ConnectionIdMap connection_id_map_;
+
+  // Pending public reset packets that need to be sent out to the client
+  // when we are given a chance to write by the dispatcher.
+  std::deque<QueuedPacket*> pending_packets_queue_;
+
+  // Used to schedule alarms to delete old connection_ids which have been in the
+  // list for too long.
+  QuicConnectionHelperInterface* helper_;
+
+  // Time period for which connection_ids should remain in time wait state.
+  const QuicTime::Delta kTimeWaitPeriod_;
+
+  // Alarm registered with the connection helper to clean up connection_ids that
+  // have
+  // out lived their duration in time wait state.
+  scoped_ptr<QuicAlarm> connection_id_clean_up_alarm_;
+
+  // Interface that writes given buffer to the socket.
+  QuicPacketWriter* writer_;
+
+  // Interface that manages blocked writers.
+  QuicServerSessionVisitor* visitor_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicTimeWaitListManager);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
diff --git a/net/quic/quic_types.cc b/net/quic/quic_types.cc
new file mode 100644
index 0000000..cdfb36d
--- /dev/null
+++ b/net/quic/quic_types.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_types.h"
+
+using std::ostream;
+
+namespace net {
+
+QuicConsumedData::QuicConsumedData(size_t bytes_consumed,
+                                   bool fin_consumed)
+      : bytes_consumed(bytes_consumed),
+        fin_consumed(fin_consumed) {
+}
+
+ostream& operator<<(ostream& os, const QuicConsumedData& s) {
+  os << "bytes_consumed: " << s.bytes_consumed
+     << " fin_consumed: " << s.fin_consumed;
+  return os;
+}
+
+WriteResult::WriteResult()
+    : status(WRITE_STATUS_ERROR),
+      bytes_written(0) {
+}
+
+WriteResult::WriteResult(WriteStatus status,
+                         int bytes_written_or_error_code)
+    : status(status),
+      bytes_written(bytes_written_or_error_code) {
+}
+
+}  // namespace net
diff --git a/net/quic/quic_types.h b/net/quic/quic_types.h
new file mode 100644
index 0000000..01415bc
--- /dev/null
+++ b/net/quic/quic_types.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_TYPES_H_
+#define NET_QUIC_QUIC_TYPES_H_
+
+// This header defines some basic types that don't depend on quic_protocol.h,
+// so that classes not directly related to the protocol wire format can avoid
+// including quic_protocol.h.
+
+#include <stddef.h>
+#include <ostream>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A struct for functions which consume data payloads and fins.
+struct NET_EXPORT_PRIVATE QuicConsumedData {
+  QuicConsumedData(size_t bytes_consumed, bool fin_consumed);
+
+  // By default, gtest prints the raw bytes of an object. The bool data
+  // member causes this object to have padding bytes, which causes the
+  // default gtest object printer to read uninitialize memory. So we need
+  // to teach gtest how to print this object.
+  NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+      std::ostream& os, const QuicConsumedData& s);
+
+  // How many bytes were consumed.
+  size_t bytes_consumed;
+
+  // True if an incoming fin was consumed.
+  bool fin_consumed;
+};
+
+// QuicAsyncStatus enumerates the possible results of an asynchronous
+// operation.
+enum QuicAsyncStatus {
+  QUIC_SUCCESS = 0,
+  QUIC_FAILURE = 1,
+  // QUIC_PENDING results from an operation that will occur asynchonously. When
+  // the operation is complete, a callback's |Run| method will be called.
+  QUIC_PENDING = 2,
+};
+
+// TODO(wtc): see if WriteStatus can be replaced by QuicAsyncStatus.
+enum WriteStatus {
+  WRITE_STATUS_OK,
+  WRITE_STATUS_BLOCKED,
+  WRITE_STATUS_ERROR,
+};
+
+// A struct used to return the result of write calls including either the number
+// of bytes written or the error code, depending upon the status.
+struct NET_EXPORT_PRIVATE WriteResult {
+  WriteResult(WriteStatus status, int bytes_written_or_error_code);
+  WriteResult();
+
+  WriteStatus status;
+  union {
+    int bytes_written;  // only valid when status is WRITE_STATUS_OK
+    int error_code;  // only valid when status is WRITE_STATUS_ERROR
+  };
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_TYPES_H_
diff --git a/net/quic/quic_unacked_packet_map.cc b/net/quic/quic_unacked_packet_map.cc
new file mode 100644
index 0000000..ab46dfb
--- /dev/null
+++ b/net/quic/quic_unacked_packet_map.cc
@@ -0,0 +1,391 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_unacked_packet_map.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_utils_chromium.h"
+
+using std::max;
+
+namespace net {
+
+QuicUnackedPacketMap::QuicUnackedPacketMap()
+    : largest_sent_packet_(0),
+      largest_observed_(0),
+      least_unacked_(1),
+      bytes_in_flight_(0),
+      pending_crypto_packet_count_(0) {
+}
+
+QuicUnackedPacketMap::~QuicUnackedPacketMap() {
+  QuicPacketSequenceNumber index = least_unacked_;
+  for (UnackedPacketMap::iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++index) {
+    delete it->retransmittable_frames;
+    // Only delete all_transmissions once, for the newest packet.
+    if (it->all_transmissions != nullptr &&
+        index == *it->all_transmissions->rbegin()) {
+      delete it->all_transmissions;
+    }
+  }
+}
+
+// TODO(ianswett): Combine this method with OnPacketSent once packets are always
+// sent in order and the connection tracks RetransmittableFrames for longer.
+void QuicUnackedPacketMap::AddPacket(
+    const SerializedPacket& serialized_packet) {
+  DCHECK_GE(serialized_packet.sequence_number,
+            least_unacked_ + unacked_packets_.size());
+  while (least_unacked_ + unacked_packets_.size() <
+         serialized_packet.sequence_number) {
+    unacked_packets_.push_back(TransmissionInfo());
+    unacked_packets_.back().is_unackable = true;
+  }
+  unacked_packets_.push_back(
+      TransmissionInfo(serialized_packet.retransmittable_frames,
+                       serialized_packet.sequence_number_length));
+  if (serialized_packet.retransmittable_frames != nullptr &&
+      serialized_packet.retransmittable_frames->HasCryptoHandshake() ==
+          IS_HANDSHAKE) {
+    ++pending_crypto_packet_count_;
+  }
+}
+
+void QuicUnackedPacketMap::RemoveObsoletePackets() {
+  while (!unacked_packets_.empty()) {
+    if (!IsPacketRemovable(least_unacked_, unacked_packets_.front())) {
+      break;
+    }
+    unacked_packets_.pop_front();
+    ++least_unacked_;
+  }
+}
+
+void QuicUnackedPacketMap::OnRetransmittedPacket(
+    QuicPacketSequenceNumber old_sequence_number,
+    QuicPacketSequenceNumber new_sequence_number,
+    TransmissionType transmission_type) {
+  DCHECK_GE(old_sequence_number, least_unacked_);
+  DCHECK_LT(old_sequence_number, least_unacked_ + unacked_packets_.size());
+  DCHECK_GE(new_sequence_number, least_unacked_ + unacked_packets_.size());
+  while (least_unacked_ + unacked_packets_.size() < new_sequence_number) {
+    unacked_packets_.push_back(TransmissionInfo());
+    unacked_packets_.back().is_unackable = true;
+  }
+
+  // TODO(ianswett): Discard and lose the packet lazily instead of immediately.
+  TransmissionInfo* transmission_info =
+      &unacked_packets_.at(old_sequence_number - least_unacked_);
+  RetransmittableFrames* frames = transmission_info->retransmittable_frames;
+  LOG_IF(DFATAL, frames == nullptr)
+      << "Attempt to retransmit packet with no "
+      << "retransmittable frames: " << old_sequence_number;
+
+  // We keep the old packet in the unacked packet list until it, or one of
+  // the retransmissions of it are acked.
+  transmission_info->retransmittable_frames = nullptr;
+  // Only keep one transmission older than largest observed, because only the
+  // most recent is expected to possibly be a spurious retransmission.
+  while (transmission_info->all_transmissions != nullptr &&
+         transmission_info->all_transmissions->size() > 1 &&
+         *(++transmission_info->all_transmissions->begin()) <
+             largest_observed_) {
+    QuicPacketSequenceNumber old_transmission =
+        *transmission_info->all_transmissions->begin();
+    TransmissionInfo* old_info =
+        &unacked_packets_[old_transmission - least_unacked_];
+    // Don't remove old packets if they're still in flight.
+    if (old_info->in_flight) {
+      break;
+    }
+    old_info->all_transmissions->pop_front();
+    // This will cause the packet be removed in RemoveObsoletePackets.
+    old_info->all_transmissions = nullptr;
+  }
+  // Don't link old transmissions to new ones when version or
+  // encryption changes.
+  if (transmission_type == ALL_INITIAL_RETRANSMISSION ||
+      transmission_type == ALL_UNACKED_RETRANSMISSION) {
+    RemoveAckability(transmission_info);
+  } else {
+    if (transmission_info->all_transmissions == nullptr) {
+      transmission_info->all_transmissions = new SequenceNumberList();
+      transmission_info->all_transmissions->push_back(old_sequence_number);
+    }
+    transmission_info->all_transmissions->push_back(new_sequence_number);
+  }
+  unacked_packets_.push_back(
+      TransmissionInfo(frames,
+                       transmission_info->sequence_number_length,
+                       transmission_type,
+                       transmission_info->all_transmissions));
+  RemoveObsoletePackets();
+}
+
+void QuicUnackedPacketMap::ClearAllPreviousRetransmissions() {
+  while (!unacked_packets_.empty() && least_unacked_ < largest_observed_) {
+    // If this packet is in flight, or has retransmittable data, then there is
+    // no point in clearing out any further packets, because they would not
+    // affect the high water mark.
+    TransmissionInfo* info = &unacked_packets_.front();
+    if (info->in_flight || info->retransmittable_frames != nullptr) {
+      break;
+    }
+
+    if (info->all_transmissions != nullptr) {
+      if (info->all_transmissions->size() < 2) {
+        LOG(DFATAL) << "all_transmissions must be nullptr or have multiple "
+                    << "elements.  size:" << info->all_transmissions->size();
+        delete info->all_transmissions;
+      } else {
+        info->all_transmissions->pop_front();
+        if (info->all_transmissions->size() == 1) {
+          // Set the newer transmission's 'all_transmissions' entry to nullptr.
+          QuicPacketSequenceNumber new_transmission =
+              info->all_transmissions->front();
+          TransmissionInfo* new_info =
+              &unacked_packets_.at(new_transmission - least_unacked_);
+          delete new_info->all_transmissions;
+          new_info->all_transmissions = nullptr;
+        }
+      }
+    }
+    unacked_packets_.pop_front();
+    ++least_unacked_;
+  }
+}
+
+bool QuicUnackedPacketMap::HasRetransmittableFrames(
+    QuicPacketSequenceNumber sequence_number) const {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  return unacked_packets_[sequence_number - least_unacked_]
+             .retransmittable_frames != nullptr;
+}
+
+void QuicUnackedPacketMap::NackPacket(QuicPacketSequenceNumber sequence_number,
+                                      size_t min_nacks) {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  unacked_packets_[sequence_number - least_unacked_].nack_count =
+      max(min_nacks,
+          unacked_packets_[sequence_number - least_unacked_].nack_count);
+}
+
+void QuicUnackedPacketMap::RemoveRetransmittability(
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  TransmissionInfo* info = &unacked_packets_[sequence_number - least_unacked_];
+  SequenceNumberList* all_transmissions = info->all_transmissions;
+  if (all_transmissions == nullptr) {
+    MaybeRemoveRetransmittableFrames(info);
+    return;
+  }
+  // TODO(ianswett): Consider adding a check to ensure there are retransmittable
+  // frames associated with this packet.
+  for (SequenceNumberList::const_iterator it = all_transmissions->begin();
+       it != all_transmissions->end(); ++it) {
+    TransmissionInfo* transmission_info =
+        &unacked_packets_[*it - least_unacked_];
+    MaybeRemoveRetransmittableFrames(transmission_info);
+    transmission_info->all_transmissions = nullptr;
+  }
+  delete all_transmissions;
+}
+
+void QuicUnackedPacketMap::RemoveAckability(TransmissionInfo* info) {
+  DCHECK(info->retransmittable_frames == nullptr);
+  info->is_unackable = true;
+  SequenceNumberList* all_transmissions = info->all_transmissions;
+  if (all_transmissions == nullptr) {
+    return;
+  }
+  for (SequenceNumberList::const_iterator it = all_transmissions->begin();
+       it != all_transmissions->end(); ++it) {
+    TransmissionInfo* transmission_info =
+        &unacked_packets_[*it - least_unacked_];
+    transmission_info->all_transmissions = nullptr;
+    transmission_info->is_unackable = true;
+  }
+  delete all_transmissions;
+}
+
+void QuicUnackedPacketMap::MaybeRemoveRetransmittableFrames(
+    TransmissionInfo* transmission_info) {
+  if (transmission_info->retransmittable_frames != nullptr) {
+    if (transmission_info->retransmittable_frames->HasCryptoHandshake()
+            == IS_HANDSHAKE) {
+      --pending_crypto_packet_count_;
+    }
+    delete transmission_info->retransmittable_frames;
+    transmission_info->retransmittable_frames = nullptr;
+  }
+}
+
+void QuicUnackedPacketMap::IncreaseLargestObserved(
+    QuicPacketSequenceNumber largest_observed) {
+  DCHECK_LE(largest_observed_, largest_observed);
+  largest_observed_ = largest_observed;
+}
+
+bool QuicUnackedPacketMap::IsPacketUseless(
+    QuicPacketSequenceNumber sequence_number,
+    const TransmissionInfo& info) const {
+  return (info.is_unackable || sequence_number <= largest_observed_) &&
+         !info.in_flight && info.retransmittable_frames == nullptr &&
+         info.all_transmissions == nullptr;
+}
+
+bool QuicUnackedPacketMap::IsPacketRemovable(
+    QuicPacketSequenceNumber sequence_number,
+    const TransmissionInfo& info) const {
+  return (info.is_unackable || sequence_number <= largest_observed_ ||
+          unacked_packets_.size() > kMaxTcpCongestionWindow) &&
+         !info.in_flight && info.retransmittable_frames == nullptr &&
+         info.all_transmissions == nullptr;
+}
+
+bool QuicUnackedPacketMap::IsUnacked(
+    QuicPacketSequenceNumber sequence_number) const {
+  if (sequence_number < least_unacked_ ||
+      sequence_number >= least_unacked_ + unacked_packets_.size()) {
+    return false;
+  }
+  return !IsPacketUseless(sequence_number,
+                          unacked_packets_[sequence_number - least_unacked_]);
+}
+
+void QuicUnackedPacketMap::RemoveFromInFlight(
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  TransmissionInfo* info = &unacked_packets_[sequence_number - least_unacked_];
+  if (info->in_flight) {
+    LOG_IF(DFATAL, bytes_in_flight_ < info->bytes_sent);
+    bytes_in_flight_ -= info->bytes_sent;
+    info->in_flight = false;
+  }
+}
+
+bool QuicUnackedPacketMap::HasUnackedPackets() const {
+  return !unacked_packets_.empty();
+}
+
+bool QuicUnackedPacketMap::HasInFlightPackets() const {
+  return bytes_in_flight_ > 0;
+}
+
+const TransmissionInfo& QuicUnackedPacketMap::GetTransmissionInfo(
+    QuicPacketSequenceNumber sequence_number) const {
+  return unacked_packets_[sequence_number - least_unacked_];
+}
+
+QuicTime QuicUnackedPacketMap::GetLastPacketSentTime() const {
+  UnackedPacketMap::const_reverse_iterator it = unacked_packets_.rbegin();
+  while (it != unacked_packets_.rend()) {
+    if (it->in_flight) {
+      LOG_IF(DFATAL, it->sent_time == QuicTime::Zero())
+          << "Sent time can never be zero for a packet in flight.";
+      return it->sent_time;
+    }
+    ++it;
+  }
+  LOG(DFATAL) << "GetLastPacketSentTime requires in flight packets.";
+  return QuicTime::Zero();
+}
+
+QuicTime QuicUnackedPacketMap::GetFirstInFlightPacketSentTime() const {
+  UnackedPacketMap::const_iterator it = unacked_packets_.begin();
+  while (it != unacked_packets_.end() && !it->in_flight) {
+    ++it;
+  }
+  if (it == unacked_packets_.end()) {
+    LOG(DFATAL) << "GetFirstInFlightPacketSentTime requires in flight packets.";
+    return QuicTime::Zero();
+  }
+  return it->sent_time;
+}
+
+size_t QuicUnackedPacketMap::GetNumUnackedPacketsDebugOnly() const {
+  size_t unacked_packet_count = 0;
+  QuicPacketSequenceNumber sequence_number = least_unacked_;
+  for (UnackedPacketMap::const_iterator it = unacked_packets_.begin();
+       it != unacked_packets_.end(); ++it, ++sequence_number) {
+    if (!IsPacketUseless(sequence_number, *it)) {
+      ++unacked_packet_count;
+    }
+  }
+  return unacked_packet_count;
+}
+
+bool QuicUnackedPacketMap::HasMultipleInFlightPackets() const {
+  size_t num_in_flight = 0;
+  for (UnackedPacketMap::const_reverse_iterator it = unacked_packets_.rbegin();
+       it != unacked_packets_.rend(); ++it) {
+    if (it->in_flight) {
+      ++num_in_flight;
+    }
+    if (num_in_flight > 1) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool QuicUnackedPacketMap::HasPendingCryptoPackets() const {
+  return pending_crypto_packet_count_ > 0;
+}
+
+bool QuicUnackedPacketMap::HasUnackedRetransmittableFrames() const {
+  for (UnackedPacketMap::const_reverse_iterator it =
+           unacked_packets_.rbegin(); it != unacked_packets_.rend(); ++it) {
+    if (it->in_flight && it->retransmittable_frames) {
+      return true;
+    }
+  }
+  return false;
+}
+
+QuicPacketSequenceNumber
+QuicUnackedPacketMap::GetLeastUnacked() const {
+  return least_unacked_;
+}
+
+void QuicUnackedPacketMap::SetSent(QuicPacketSequenceNumber sequence_number,
+                                   QuicTime sent_time,
+                                   QuicByteCount bytes_sent,
+                                   bool set_in_flight) {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  TransmissionInfo* info = &unacked_packets_[sequence_number - least_unacked_];
+  DCHECK(!info->in_flight);
+
+  DCHECK_LT(largest_sent_packet_, sequence_number);
+  largest_sent_packet_ = max(sequence_number, largest_sent_packet_);
+  info->sent_time = sent_time;
+  if (set_in_flight) {
+    bytes_in_flight_ += bytes_sent;
+    info->bytes_sent = bytes_sent;
+    info->in_flight = true;
+  }
+}
+
+void QuicUnackedPacketMap::RestoreInFlight(
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK_GE(sequence_number, least_unacked_);
+  DCHECK_LT(sequence_number, least_unacked_ + unacked_packets_.size());
+  TransmissionInfo* info = &unacked_packets_[sequence_number - least_unacked_];
+  DCHECK(!info->in_flight);
+  DCHECK_NE(0u, info->bytes_sent);
+  DCHECK(info->sent_time.IsInitialized());
+
+  bytes_in_flight_ += info->bytes_sent;
+  info->in_flight = true;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_unacked_packet_map.h b/net/quic/quic_unacked_packet_map.h
new file mode 100644
index 0000000..9806ffc
--- /dev/null
+++ b/net/quic/quic_unacked_packet_map.h
@@ -0,0 +1,174 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_QUIC_UNACKED_PACKET_MAP_H_
+#define NET_QUIC_QUIC_UNACKED_PACKET_MAP_H_
+
+#include <deque>
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// Class which tracks unacked packets for three purposes:
+// 1) Track retransmittable data, including multiple transmissions of frames.
+// 2) Track packets and bytes in flight for congestion control.
+// 3) Track sent time of packets to provide RTT measurements from acks.
+class NET_EXPORT_PRIVATE QuicUnackedPacketMap {
+ public:
+  QuicUnackedPacketMap();
+  ~QuicUnackedPacketMap();
+
+  // Adds |serialized_packet| to the map.  Does not mark it in flight.
+  void AddPacket(const SerializedPacket& serialized_packet);
+
+  // Called when a packet is retransmitted with a new sequence number.
+  // |old_sequence_number| will remain unacked, but will have no
+  // retransmittable data associated with it. |new_sequence_number| will
+  // be both unacked and associated with retransmittable data.
+  void OnRetransmittedPacket(QuicPacketSequenceNumber old_sequence_number,
+                             QuicPacketSequenceNumber new_sequence_number,
+                             TransmissionType transmission_type);
+
+  // Returns true if the packet |sequence_number| is unacked.
+  bool IsUnacked(QuicPacketSequenceNumber sequence_number) const;
+
+  // Sets the nack count to the max of the current nack count and |min_nacks|.
+  void NackPacket(QuicPacketSequenceNumber sequence_number,
+                  size_t min_nacks);
+
+  // Marks |sequence_number| as no longer in flight.
+  void RemoveFromInFlight(QuicPacketSequenceNumber sequence_number);
+
+  // Returns true if the unacked packet |sequence_number| has retransmittable
+  // frames.  This will return false if the packet has been acked, if a
+  // previous transmission of this packet was ACK'd, or if this packet has been
+  // retransmitted as with different sequence number, or if the packet never
+  // had any retransmittable packets in the first place.
+  bool HasRetransmittableFrames(QuicPacketSequenceNumber sequence_number) const;
+
+  // Returns true if there are any unacked packets.
+  bool HasUnackedPackets() const;
+
+  // Returns true if there are any unacked packets which have retransmittable
+  // frames.
+  bool HasUnackedRetransmittableFrames() const;
+
+  // Returns the largest sequence number that has been sent.
+  QuicPacketSequenceNumber largest_sent_packet() const {
+    return largest_sent_packet_;
+  }
+
+  // Returns the largest sequence number that has been acked.
+  QuicPacketSequenceNumber largest_observed() const {
+    return largest_observed_;
+  }
+
+  // Returns the sum of bytes from all packets in flight.
+  QuicByteCount bytes_in_flight() const {
+    return bytes_in_flight_;
+  }
+
+  // Returns the smallest sequence number of a serialized packet which has not
+  // been acked by the peer.  If there are no unacked packets, returns 0.
+  QuicPacketSequenceNumber GetLeastUnacked() const;
+
+  // Sets a packet as sent with the sent time |sent_time|.  Marks the packet
+  // as in flight if |set_in_flight| is true.
+  // Packets marked as in flight are expected to be marked as missing when they
+  // don't arrive, indicating the need for retransmission.
+  void SetSent(QuicPacketSequenceNumber sequence_number,
+               QuicTime sent_time,
+               QuicByteCount bytes_sent,
+               bool set_in_flight);
+
+  // Restores the in flight status for a packet that was previously sent.
+  void RestoreInFlight(QuicPacketSequenceNumber sequence_number);
+
+  // Clears all previous transmissions in order to make room in the ack frame
+  // for newly acked packets.
+  void ClearAllPreviousRetransmissions();
+
+  typedef std::deque<TransmissionInfo> UnackedPacketMap;
+
+  typedef UnackedPacketMap::const_iterator const_iterator;
+
+  const_iterator begin() const { return unacked_packets_.begin(); }
+  const_iterator end() const { return unacked_packets_.end(); }
+
+  // Returns true if there are unacked packets that are in flight.
+  bool HasInFlightPackets() const;
+
+  // Returns the TransmissionInfo associated with |sequence_number|, which
+  // must be unacked.
+  const TransmissionInfo& GetTransmissionInfo(
+      QuicPacketSequenceNumber sequence_number) const;
+
+  // Returns the time that the last unacked packet was sent.
+  QuicTime GetLastPacketSentTime() const;
+
+  // Returns the time that the first in flight packet was sent.
+  QuicTime GetFirstInFlightPacketSentTime() const;
+
+  // Returns the number of unacked packets.
+  size_t GetNumUnackedPacketsDebugOnly() const;
+
+  // Returns true if there are multiple packets in flight.
+  bool HasMultipleInFlightPackets() const;
+
+  // Returns true if there are any pending crypto packets.
+  bool HasPendingCryptoPackets() const;
+
+  // Removes any retransmittable frames from this transmission or an associated
+  // transmission.  It removes now useless transmissions, and disconnects any
+  // other packets from other transmissions.
+  void RemoveRetransmittability(QuicPacketSequenceNumber sequence_number);
+
+  // Removes any other retransmissions and marks all transmissions unackable.
+  void RemoveAckability(TransmissionInfo* info);
+
+  // Increases the largest observed.  Any packets less or equal to
+  // |largest_acked_packet| are discarded if they are only for the RTT purposes.
+  void IncreaseLargestObserved(QuicPacketSequenceNumber largest_observed);
+
+  // Remove any packets no longer needed for retransmission, congestion, or
+  // RTT measurement purposes.
+  void RemoveObsoletePackets();
+
+ private:
+  void MaybeRemoveRetransmittableFrames(TransmissionInfo* transmission_info);
+
+  // Returns true if the packet no longer has a purpose in the map.
+  bool IsPacketUseless(QuicPacketSequenceNumber sequence_number,
+                       const TransmissionInfo& info) const;
+  // Returns true if the packet is useless or it's only purpose is RTT
+  // measurement, and it's old enough that is unlikely to ever happen.
+  bool IsPacketRemovable(QuicPacketSequenceNumber sequence_number,
+                         const TransmissionInfo& info) const;
+
+  QuicPacketSequenceNumber largest_sent_packet_;
+  QuicPacketSequenceNumber largest_observed_;
+
+  // Newly serialized retransmittable and fec packets are added to this map,
+  // which contains owning pointers to any contained frames.  If a packet is
+  // retransmitted, this map will contain entries for both the old and the new
+  // packet. The old packet's retransmittable frames entry will be nullptr,
+  // while the new packet's entry will contain the frames to retransmit.
+  // If the old packet is acked before the new packet, then the old entry will
+  // be removed from the map and the new entry's retransmittable frames will be
+  // set to nullptr.
+  UnackedPacketMap unacked_packets_;
+  // The packet at the 0th index of unacked_packets_.
+  QuicPacketSequenceNumber least_unacked_;
+
+  size_t bytes_in_flight_;
+  // Number of retransmittable crypto handshake packets.
+  size_t pending_crypto_packet_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicUnackedPacketMap);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_UNACKED_PACKET_MAP_H_
diff --git a/net/quic/quic_unacked_packet_map_test.cc b/net/quic/quic_unacked_packet_map_test.cc
new file mode 100644
index 0000000..4f5d6a5
--- /dev/null
+++ b/net/quic/quic_unacked_packet_map_test.cc
@@ -0,0 +1,371 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_unacked_packet_map.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::min;
+
+namespace net {
+namespace test {
+namespace {
+
+// Default packet length.
+const uint32 kDefaultAckLength = 50;
+const uint32 kDefaultLength = 1000;
+
+class QuicUnackedPacketMapTest : public ::testing::Test {
+ protected:
+  QuicUnackedPacketMapTest()
+      : now_(QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(1000))) {
+  }
+
+  SerializedPacket CreateRetransmittablePacket(
+      QuicPacketSequenceNumber sequence_number) {
+    return SerializedPacket(sequence_number, PACKET_1BYTE_SEQUENCE_NUMBER,
+                            nullptr, 0, new RetransmittableFrames());
+  }
+
+  SerializedPacket CreateNonRetransmittablePacket(
+      QuicPacketSequenceNumber sequence_number) {
+    return SerializedPacket(sequence_number, PACKET_1BYTE_SEQUENCE_NUMBER,
+                            nullptr, 0, nullptr);
+  }
+
+  void VerifyInFlightPackets(QuicPacketSequenceNumber* packets,
+                             size_t num_packets) {
+    unacked_packets_.RemoveObsoletePackets();
+    if (num_packets == 0) {
+      EXPECT_FALSE(unacked_packets_.HasInFlightPackets());
+      EXPECT_FALSE(unacked_packets_.HasMultipleInFlightPackets());
+      return;
+    }
+    if (num_packets == 1) {
+      EXPECT_TRUE(unacked_packets_.HasInFlightPackets());
+      EXPECT_FALSE(unacked_packets_.HasMultipleInFlightPackets());
+      ASSERT_TRUE(unacked_packets_.IsUnacked(packets[0]));
+      EXPECT_TRUE(unacked_packets_.GetTransmissionInfo(packets[0]).in_flight);
+    }
+    for (size_t i = 0; i < num_packets; ++i) {
+      ASSERT_TRUE(unacked_packets_.IsUnacked(packets[i]));
+      EXPECT_TRUE(unacked_packets_.GetTransmissionInfo(packets[i]).in_flight);
+    }
+    size_t in_flight_count = 0;
+    for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+         it != unacked_packets_.end(); ++it) {
+      if (it->in_flight) {
+        ++in_flight_count;
+      }
+    }
+    EXPECT_EQ(num_packets, in_flight_count);
+  }
+
+  void VerifyUnackedPackets(QuicPacketSequenceNumber* packets,
+                            size_t num_packets) {
+    unacked_packets_.RemoveObsoletePackets();
+    if (num_packets == 0) {
+      EXPECT_FALSE(unacked_packets_.HasUnackedPackets());
+      EXPECT_FALSE(unacked_packets_.HasUnackedRetransmittableFrames());
+      return;
+    }
+    EXPECT_TRUE(unacked_packets_.HasUnackedPackets());
+    for (size_t i = 0; i < num_packets; ++i) {
+      EXPECT_TRUE(unacked_packets_.IsUnacked(packets[i])) << packets[i];
+    }
+    EXPECT_EQ(num_packets, unacked_packets_.GetNumUnackedPacketsDebugOnly());
+  }
+
+  void VerifyRetransmittablePackets(QuicPacketSequenceNumber* packets,
+                                    size_t num_packets) {
+    unacked_packets_.RemoveObsoletePackets();
+    size_t num_retransmittable_packets = 0;
+    for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin();
+         it != unacked_packets_.end(); ++it) {
+      if (it->retransmittable_frames != nullptr) {
+        ++num_retransmittable_packets;
+      }
+    }
+    EXPECT_EQ(num_packets, num_retransmittable_packets);
+    for (size_t i = 0; i < num_packets; ++i) {
+      EXPECT_TRUE(unacked_packets_.HasRetransmittableFrames(packets[i]))
+          << " packets[" << i << "]:" << packets[i];
+    }
+  }
+
+  QuicUnackedPacketMap unacked_packets_;
+  QuicTime now_;
+};
+
+TEST_F(QuicUnackedPacketMapTest, RttOnly) {
+  // Acks are only tracked for RTT measurement purposes.
+  unacked_packets_.AddPacket(CreateNonRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultAckLength, false);
+
+  QuicPacketSequenceNumber unacked[] = { 1 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.IncreaseLargestObserved(1);
+  VerifyUnackedPackets(nullptr, 0);
+  VerifyInFlightPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicUnackedPacketMapTest, DiscardOldRttOnly) {
+  // Acks are only tracked for RTT measurement purposes, and are discarded
+  // when more than 200 accumulate.
+  const size_t kNumUnackedPackets = 200;
+  for (size_t i = 1; i < 400; ++i) {
+    unacked_packets_.AddPacket(CreateNonRetransmittablePacket(i));
+    unacked_packets_.SetSent(i, now_, kDefaultAckLength, false);
+    unacked_packets_.RemoveObsoletePackets();
+    EXPECT_EQ(min(i, kNumUnackedPackets),
+              unacked_packets_.GetNumUnackedPacketsDebugOnly());
+  }
+}
+
+TEST_F(QuicUnackedPacketMapTest, RetransmittableInflightAndRtt) {
+  // Simulate a retransmittable packet being sent and acked.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked[] = { 1 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(unacked, arraysize(unacked));
+
+  unacked_packets_.RemoveRetransmittability(1);
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.IncreaseLargestObserved(1);
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.RemoveFromInFlight(1);
+  VerifyUnackedPackets(nullptr, 0);
+  VerifyInFlightPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicUnackedPacketMapTest, RetransmittedPacket) {
+  // Simulate a retransmittable packet being sent, retransmitted, and the first
+  // transmission being acked.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+  unacked_packets_.OnRetransmittedPacket(1, 2, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(2, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 2 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+
+  unacked_packets_.RemoveRetransmittability(1);
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.IncreaseLargestObserved(2);
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.RemoveFromInFlight(2);
+  QuicPacketSequenceNumber unacked2[] = { 1 };
+  VerifyUnackedPackets(unacked, arraysize(unacked2));
+  VerifyInFlightPackets(unacked, arraysize(unacked2));
+  VerifyRetransmittablePackets(nullptr, 0);
+
+  unacked_packets_.RemoveFromInFlight(1);
+  VerifyUnackedPackets(nullptr, 0);
+  VerifyInFlightPackets(nullptr, 0);
+  VerifyRetransmittablePackets(nullptr, 0);
+}
+
+TEST_F(QuicUnackedPacketMapTest, RetransmitThreeTimes) {
+  // Simulate a retransmittable packet being sent and retransmitted twice.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(2));
+  unacked_packets_.SetSent(2, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 1, 2 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+
+  // Early retransmit 1 as 3 and send new data as 4.
+  unacked_packets_.IncreaseLargestObserved(2);
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.RemoveRetransmittability(2);
+  unacked_packets_.RemoveFromInFlight(1);
+  unacked_packets_.OnRetransmittedPacket(1, 3, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(3, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(4));
+  unacked_packets_.SetSent(4, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked2[] = { 1, 3, 4 };
+  VerifyUnackedPackets(unacked2, arraysize(unacked2));
+  QuicPacketSequenceNumber pending2[] = { 3, 4, };
+  VerifyInFlightPackets(pending2, arraysize(pending2));
+  QuicPacketSequenceNumber retransmittable2[] = { 3, 4 };
+  VerifyRetransmittablePackets(retransmittable2, arraysize(retransmittable2));
+
+  // Early retransmit 3 (formerly 1) as 5, and remove 1 from unacked.
+  unacked_packets_.IncreaseLargestObserved(4);
+  unacked_packets_.RemoveFromInFlight(4);
+  unacked_packets_.RemoveRetransmittability(4);
+  unacked_packets_.OnRetransmittedPacket(3, 5, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(5, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(6));
+  unacked_packets_.SetSent(6, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked3[] = { 3, 5, 6 };
+  VerifyUnackedPackets(unacked3, arraysize(unacked3));
+  QuicPacketSequenceNumber pending3[] = { 3, 5, 6 };
+  VerifyInFlightPackets(pending3, arraysize(pending3));
+  QuicPacketSequenceNumber retransmittable3[] = { 5, 6 };
+  VerifyRetransmittablePackets(retransmittable3, arraysize(retransmittable3));
+
+  // Early retransmit 5 as 7 and ensure in flight packet 3 is not removed.
+  unacked_packets_.IncreaseLargestObserved(6);
+  unacked_packets_.RemoveFromInFlight(6);
+  unacked_packets_.RemoveRetransmittability(6);
+  unacked_packets_.OnRetransmittedPacket(5, 7, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(7, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked4[] = { 3, 5, 7 };
+  VerifyUnackedPackets(unacked4, arraysize(unacked4));
+  QuicPacketSequenceNumber pending4[] = { 3, 5, 7 };
+  VerifyInFlightPackets(pending4, arraysize(pending4));
+  QuicPacketSequenceNumber retransmittable4[] = { 7 };
+  VerifyRetransmittablePackets(retransmittable4, arraysize(retransmittable4));
+
+  // Remove the older two transmissions from in flight.
+  unacked_packets_.RemoveFromInFlight(3);
+  unacked_packets_.RemoveFromInFlight(5);
+  QuicPacketSequenceNumber pending5[] = { 7 };
+  VerifyInFlightPackets(pending5, arraysize(pending5));
+
+  // Now test ClearAllPreviousTransmissions, leaving one packet.
+  unacked_packets_.ClearAllPreviousRetransmissions();
+  QuicPacketSequenceNumber unacked5[] = { 7 };
+  VerifyUnackedPackets(unacked5, arraysize(unacked5));
+  QuicPacketSequenceNumber retransmittable5[] = { 7 };
+  VerifyRetransmittablePackets(retransmittable5, arraysize(retransmittable5));
+}
+
+TEST_F(QuicUnackedPacketMapTest, RetransmitFourTimes) {
+  // Simulate a retransmittable packet being sent and retransmitted twice.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(2));
+  unacked_packets_.SetSent(2, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 1, 2 };
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+
+  // Early retransmit 1 as 3.
+  unacked_packets_.IncreaseLargestObserved(2);
+  unacked_packets_.RemoveFromInFlight(2);
+  unacked_packets_.RemoveRetransmittability(2);
+  unacked_packets_.RemoveFromInFlight(1);
+  unacked_packets_.OnRetransmittedPacket(1, 3, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(3, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked2[] = { 1, 3 };
+  VerifyUnackedPackets(unacked2, arraysize(unacked2));
+  QuicPacketSequenceNumber pending2[] = { 3 };
+  VerifyInFlightPackets(pending2, arraysize(pending2));
+  QuicPacketSequenceNumber retransmittable2[] = { 3 };
+  VerifyRetransmittablePackets(retransmittable2, arraysize(retransmittable2));
+
+  // TLP 3 (formerly 1) as 4, and don't remove 1 from unacked.
+  unacked_packets_.OnRetransmittedPacket(3, 4, TLP_RETRANSMISSION);
+  unacked_packets_.SetSent(4, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(5));
+  unacked_packets_.SetSent(5, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked3[] = { 1, 3, 4, 5 };
+  VerifyUnackedPackets(unacked3, arraysize(unacked3));
+  QuicPacketSequenceNumber pending3[] = { 3, 4, 5 };
+  VerifyInFlightPackets(pending3, arraysize(pending3));
+  QuicPacketSequenceNumber retransmittable3[] = { 4, 5 };
+  VerifyRetransmittablePackets(retransmittable3, arraysize(retransmittable3));
+
+  // Early retransmit 4 as 6 and ensure in flight packet 3 is removed.
+  unacked_packets_.IncreaseLargestObserved(5);
+  unacked_packets_.RemoveFromInFlight(5);
+  unacked_packets_.RemoveRetransmittability(5);
+  unacked_packets_.RemoveFromInFlight(3);
+  unacked_packets_.RemoveFromInFlight(4);
+  unacked_packets_.OnRetransmittedPacket(4, 6, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(6, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked4[] = { 4, 6 };
+  VerifyUnackedPackets(unacked4, arraysize(unacked4));
+  QuicPacketSequenceNumber pending4[] = { 6 };
+  VerifyInFlightPackets(pending4, arraysize(pending4));
+  QuicPacketSequenceNumber retransmittable4[] = { 6 };
+  VerifyRetransmittablePackets(retransmittable4, arraysize(retransmittable4));
+}
+
+TEST_F(QuicUnackedPacketMapTest, RestoreInflight) {
+  // Simulate a retransmittable packet being sent, retransmitted, and the first
+  // transmission being acked.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+  unacked_packets_.OnRetransmittedPacket(1, 2, RTO_RETRANSMISSION);
+  unacked_packets_.RemoveFromInFlight(1);
+  unacked_packets_.SetSent(2, now_, kDefaultLength, true);
+
+  QuicPacketSequenceNumber unacked[] = { 1, 2 };
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  QuicPacketSequenceNumber retransmittable[] = { 2 };
+  VerifyInFlightPackets(retransmittable, arraysize(retransmittable));
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+  EXPECT_EQ(kDefaultLength, unacked_packets_.bytes_in_flight());
+
+  // Simulate an F-RTO, and restore 1 to flight.
+  unacked_packets_.RestoreInFlight(1);
+  VerifyUnackedPackets(unacked, arraysize(unacked));
+  VerifyInFlightPackets(unacked, arraysize(unacked));
+  VerifyRetransmittablePackets(retransmittable, arraysize(retransmittable));
+  EXPECT_EQ(2 * kDefaultLength, unacked_packets_.bytes_in_flight());
+}
+
+TEST_F(QuicUnackedPacketMapTest, SendWithGap) {
+  // Simulate a retransmittable packet being sent, retransmitted, and the first
+  // transmission being acked.
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(1));
+  unacked_packets_.SetSent(1, now_, kDefaultLength, true);
+  unacked_packets_.AddPacket(CreateRetransmittablePacket(3));
+  unacked_packets_.SetSent(3, now_, kDefaultLength, true);
+  unacked_packets_.OnRetransmittedPacket(1, 5, LOSS_RETRANSMISSION);
+  unacked_packets_.SetSent(5, now_, kDefaultLength, true);
+
+  EXPECT_EQ(1u, unacked_packets_.GetLeastUnacked());
+  EXPECT_TRUE(unacked_packets_.IsUnacked(1));
+  EXPECT_FALSE(unacked_packets_.IsUnacked(2));
+  EXPECT_TRUE(unacked_packets_.IsUnacked(3));
+  EXPECT_FALSE(unacked_packets_.IsUnacked(4));
+  EXPECT_TRUE(unacked_packets_.IsUnacked(5));
+  EXPECT_EQ(5u, unacked_packets_.largest_sent_packet());
+}
+
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_utils.cc b/net/quic/quic_utils.cc
new file mode 100644
index 0000000..f8cf47a
--- /dev/null
+++ b/net/quic/quic_utils.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_utils.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_write_blocked_list.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// static
+uint64 QuicUtils::FNV1a_64_Hash(const char* data, int len) {
+  static const uint64 kOffset = GG_UINT64_C(14695981039346656037);
+  static const uint64 kPrime = GG_UINT64_C(1099511628211);
+
+  const uint8* octets = reinterpret_cast<const uint8*>(data);
+
+  uint64 hash = kOffset;
+
+  for (int i = 0; i < len; ++i) {
+    hash = hash ^ octets[i];
+    hash = hash * kPrime;
+  }
+
+  return hash;
+}
+
+// static
+uint128 QuicUtils::FNV1a_128_Hash(const char* data, int len) {
+  // The following two constants are defined as part of the hash algorithm.
+  // see http://www.isthe.com/chongo/tech/comp/fnv/
+  // 309485009821345068724781371
+  const uint128 kPrime(16777216, 315);
+  // 144066263297769815596495629667062367629
+  const uint128 kOffset(GG_UINT64_C(7809847782465536322),
+                        GG_UINT64_C(7113472399480571277));
+
+  const uint8* octets = reinterpret_cast<const uint8*>(data);
+
+  uint128 hash = kOffset;
+
+  for (int i = 0; i < len; ++i) {
+    hash  = hash ^ uint128(0, octets[i]);
+    hash = hash * kPrime;
+  }
+
+  return hash;
+}
+
+// static
+bool QuicUtils::FindMutualTag(const QuicTagVector& our_tags_vector,
+                              const QuicTag* their_tags,
+                              size_t num_their_tags,
+                              Priority priority,
+                              QuicTag* out_result,
+                              size_t* out_index) {
+  if (our_tags_vector.empty()) {
+    return false;
+  }
+  const size_t num_our_tags = our_tags_vector.size();
+  const QuicTag* our_tags = &our_tags_vector[0];
+
+  size_t num_priority_tags, num_inferior_tags;
+  const QuicTag* priority_tags;
+  const QuicTag* inferior_tags;
+  if (priority == LOCAL_PRIORITY) {
+    num_priority_tags = num_our_tags;
+    priority_tags = our_tags;
+    num_inferior_tags = num_their_tags;
+    inferior_tags = their_tags;
+  } else {
+    num_priority_tags = num_their_tags;
+    priority_tags = their_tags;
+    num_inferior_tags = num_our_tags;
+    inferior_tags = our_tags;
+  }
+
+  for (size_t i = 0; i < num_priority_tags; i++) {
+    for (size_t j = 0; j < num_inferior_tags; j++) {
+      if (priority_tags[i] == inferior_tags[j]) {
+        *out_result = priority_tags[i];
+        if (out_index) {
+          if (priority == LOCAL_PRIORITY) {
+            *out_index = j;
+          } else {
+            *out_index = i;
+          }
+        }
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+// static
+void QuicUtils::SerializeUint128(uint128 v, uint8* out) {
+  const uint64 lo = Uint128Low64(v);
+  const uint64 hi = Uint128High64(v);
+  // This assumes that the system is little-endian.
+  memcpy(out, &lo, sizeof(lo));
+  memcpy(out + sizeof(lo), &hi, sizeof(hi));
+}
+
+// static
+void QuicUtils::SerializeUint128Short(uint128 v, uint8* out) {
+  const uint64 lo = Uint128Low64(v);
+  const uint64 hi = Uint128High64(v);
+  // This assumes that the system is little-endian.
+  memcpy(out, &lo, sizeof(lo));
+  memcpy(out + sizeof(lo), &hi, sizeof(hi) / 2);
+}
+
+#define RETURN_STRING_LITERAL(x) \
+case x: \
+return #x;
+
+// static
+const char* QuicUtils::StreamErrorToString(QuicRstStreamErrorCode error) {
+  switch (error) {
+    RETURN_STRING_LITERAL(QUIC_STREAM_NO_ERROR);
+    RETURN_STRING_LITERAL(QUIC_STREAM_CONNECTION_ERROR);
+    RETURN_STRING_LITERAL(QUIC_ERROR_PROCESSING_STREAM);
+    RETURN_STRING_LITERAL(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+    RETURN_STRING_LITERAL(QUIC_BAD_APPLICATION_PAYLOAD);
+    RETURN_STRING_LITERAL(QUIC_STREAM_PEER_GOING_AWAY);
+    RETURN_STRING_LITERAL(QUIC_STREAM_CANCELLED);
+    RETURN_STRING_LITERAL(QUIC_RST_FLOW_CONTROL_ACCOUNTING);
+    RETURN_STRING_LITERAL(QUIC_STREAM_LAST_ERROR);
+  }
+  // Return a default value so that we return this when |error| doesn't match
+  // any of the QuicRstStreamErrorCodes. This can happen when the RstStream
+  // frame sent by the peer (attacker) has invalid error code.
+  return "INVALID_RST_STREAM_ERROR_CODE";
+}
+
+// static
+const char* QuicUtils::ErrorToString(QuicErrorCode error) {
+  switch (error) {
+    RETURN_STRING_LITERAL(QUIC_NO_ERROR);
+    RETURN_STRING_LITERAL(QUIC_INTERNAL_ERROR);
+    RETURN_STRING_LITERAL(QUIC_STREAM_DATA_AFTER_TERMINATION);
+    RETURN_STRING_LITERAL(QUIC_INVALID_PACKET_HEADER);
+    RETURN_STRING_LITERAL(QUIC_INVALID_FRAME_DATA);
+    RETURN_STRING_LITERAL(QUIC_MISSING_PAYLOAD);
+    RETURN_STRING_LITERAL(QUIC_INVALID_FEC_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_DATA);
+    RETURN_STRING_LITERAL(QUIC_UNENCRYPTED_STREAM_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_RST_STREAM_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_CONNECTION_CLOSE_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_GOAWAY_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_WINDOW_UPDATE_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_BLOCKED_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_STOP_WAITING_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_ACK_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_CONGESTION_FEEDBACK_DATA);
+    RETURN_STRING_LITERAL(QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+    RETURN_STRING_LITERAL(QUIC_INVALID_PUBLIC_RST_PACKET);
+    RETURN_STRING_LITERAL(QUIC_DECRYPTION_FAILURE);
+    RETURN_STRING_LITERAL(QUIC_ENCRYPTION_FAILURE);
+    RETURN_STRING_LITERAL(QUIC_PACKET_TOO_LARGE);
+    RETURN_STRING_LITERAL(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+    RETURN_STRING_LITERAL(QUIC_PEER_GOING_AWAY);
+    RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_TAGS_OUT_OF_ORDER);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_ENTRIES);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_REJECTS);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_INVALID_VALUE_LENGTH)
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_INTERNAL_ERROR);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_VERSION_NOT_SUPPORTED);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_NO_SUPPORT);
+    RETURN_STRING_LITERAL(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+    RETURN_STRING_LITERAL(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND);
+    RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_ID);
+    RETURN_STRING_LITERAL(QUIC_INVALID_PRIORITY);
+    RETURN_STRING_LITERAL(QUIC_TOO_MANY_OPEN_STREAMS);
+    RETURN_STRING_LITERAL(QUIC_TOO_MANY_UNFINISHED_STREAMS);
+    RETURN_STRING_LITERAL(QUIC_PUBLIC_RESET);
+    RETURN_STRING_LITERAL(QUIC_INVALID_VERSION);
+    RETURN_STRING_LITERAL(QUIC_INVALID_HEADER_ID);
+    RETURN_STRING_LITERAL(QUIC_INVALID_NEGOTIATED_VALUE);
+    RETURN_STRING_LITERAL(QUIC_DECOMPRESSION_FAILURE);
+    RETURN_STRING_LITERAL(QUIC_CONNECTION_TIMED_OUT);
+    RETURN_STRING_LITERAL(QUIC_CONNECTION_OVERALL_TIMED_OUT);
+    RETURN_STRING_LITERAL(QUIC_ERROR_MIGRATING_ADDRESS);
+    RETURN_STRING_LITERAL(QUIC_PACKET_WRITE_ERROR);
+    RETURN_STRING_LITERAL(QUIC_PACKET_READ_ERROR);
+    RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_FRAME);
+    RETURN_STRING_LITERAL(QUIC_INVALID_HEADERS_STREAM_DATA);
+    RETURN_STRING_LITERAL(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA);
+    RETURN_STRING_LITERAL(QUIC_FLOW_CONTROL_SENT_TOO_MUCH_DATA);
+    RETURN_STRING_LITERAL(QUIC_FLOW_CONTROL_INVALID_WINDOW);
+    RETURN_STRING_LITERAL(QUIC_CONNECTION_IP_POOLED);
+    RETURN_STRING_LITERAL(QUIC_PROOF_INVALID);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_DUPLICATE_TAG);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_SERVER_CONFIG_EXPIRED);
+    RETURN_STRING_LITERAL(QUIC_INVALID_CHANNEL_ID_SIGNATURE);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_WHILE_VALIDATING_CLIENT_HELLO);
+    RETURN_STRING_LITERAL(QUIC_CRYPTO_UPDATE_BEFORE_HANDSHAKE_COMPLETE);
+    RETURN_STRING_LITERAL(QUIC_VERSION_NEGOTIATION_MISMATCH);
+    RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
+    // Intentionally have no default case, so we'll break the build
+    // if we add errors and don't put them here.
+  }
+  // Return a default value so that we return this when |error| doesn't match
+  // any of the QuicErrorCodes. This can happen when the ConnectionClose
+  // frame sent by the peer (attacker) has invalid error code.
+  return "INVALID_ERROR_CODE";
+}
+
+// static
+const char* QuicUtils::EncryptionLevelToString(EncryptionLevel level) {
+  switch (level) {
+    RETURN_STRING_LITERAL(ENCRYPTION_NONE);
+    RETURN_STRING_LITERAL(ENCRYPTION_INITIAL);
+    RETURN_STRING_LITERAL(ENCRYPTION_FORWARD_SECURE);
+    RETURN_STRING_LITERAL(NUM_ENCRYPTION_LEVELS);
+  }
+  return "INVALID_ENCRYPTION_LEVEL";
+}
+
+// static
+const char* QuicUtils::TransmissionTypeToString(TransmissionType type) {
+  switch (type) {
+    RETURN_STRING_LITERAL(NOT_RETRANSMISSION);
+    RETURN_STRING_LITERAL(HANDSHAKE_RETRANSMISSION);
+    RETURN_STRING_LITERAL(LOSS_RETRANSMISSION);
+    RETURN_STRING_LITERAL(ALL_UNACKED_RETRANSMISSION);
+    RETURN_STRING_LITERAL(ALL_INITIAL_RETRANSMISSION);
+    RETURN_STRING_LITERAL(RTO_RETRANSMISSION);
+    RETURN_STRING_LITERAL(TLP_RETRANSMISSION);
+  }
+  return "INVALID_TRANSMISSION_TYPE";
+}
+
+// static
+string QuicUtils::TagToString(QuicTag tag) {
+  char chars[4];
+  bool ascii = true;
+  const QuicTag orig_tag = tag;
+
+  for (size_t i = 0; i < sizeof(chars); i++) {
+    chars[i] = tag;
+    if ((chars[i] == 0 || chars[i] == '\xff') && i == 3) {
+      chars[i] = ' ';
+    }
+    if (!isprint(static_cast<unsigned char>(chars[i]))) {
+      ascii = false;
+      break;
+    }
+    tag >>= 8;
+  }
+
+  if (ascii) {
+    return string(chars, sizeof(chars));
+  }
+
+  return base::UintToString(orig_tag);
+}
+
+// static
+string QuicUtils::StringToHexASCIIDump(StringPiece in_buffer) {
+  int offset = 0;
+  const int kBytesPerLine = 16;   // Max bytes dumped per line
+  const char* buf = in_buffer.data();
+  int bytes_remaining = in_buffer.size();
+  string s;   // our output
+  const char* p = buf;
+  while (bytes_remaining > 0) {
+    const int line_bytes = std::min(bytes_remaining, kBytesPerLine);
+    base::StringAppendF(&s, "0x%04x:  ", offset);  // Do the line header
+    for (int i = 0; i < kBytesPerLine; ++i) {
+      if (i < line_bytes) {
+        base::StringAppendF(&s, "%02x", static_cast<unsigned char>(p[i]));
+      } else {
+        s += "  ";    // two-space filler instead of two-space hex digits
+      }
+      if (i % 2) s += ' ';
+    }
+    s += ' ';
+    for (int i = 0; i < line_bytes; ++i) {  // Do the ASCII dump
+      s+= (p[i] >  32 && p[i] < 127) ? p[i] : '.';
+    }
+
+    bytes_remaining -= line_bytes;
+    offset += line_bytes;
+    p += line_bytes;
+    s += '\n';
+  }
+  return s;
+}
+
+// static
+QuicPriority QuicUtils::LowestPriority() {
+  return QuicWriteBlockedList::kLowestPriority;
+}
+
+// static
+QuicPriority QuicUtils::HighestPriority() {
+  return QuicWriteBlockedList::kHighestPriority;
+}
+
+}  // namespace net
diff --git a/net/quic/quic_utils.h b/net/quic/quic_utils.h
new file mode 100644
index 0000000..1ecb37d
--- /dev/null
+++ b/net/quic/quic_utils.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Some helpers for quic.
+
+#ifndef NET_QUIC_QUIC_UTILS_H_
+#define NET_QUIC_QUIC_UTILS_H_
+
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicUtils {
+ public:
+  enum Priority {
+    LOCAL_PRIORITY,
+    PEER_PRIORITY,
+  };
+
+  // Returns the 64 bit FNV1a hash of the data.  See
+  // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param
+  static uint64 FNV1a_64_Hash(const char* data, int len);
+
+  // returns the 128 bit FNV1a hash of the data.  See
+  // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param
+  static uint128 FNV1a_128_Hash(const char* data, int len);
+
+  // FindMutualTag sets |out_result| to the first tag in the priority list that
+  // is also in the other list and returns true. If there is no intersection it
+  // returns false.
+  //
+  // Which list has priority is determined by |priority|.
+  //
+  // If |out_index| is non-nullptr and a match is found then the index of that
+  // match in |their_tags| is written to |out_index|.
+  static bool FindMutualTag(const QuicTagVector& our_tags,
+                            const QuicTag* their_tags,
+                            size_t num_their_tags,
+                            Priority priority,
+                            QuicTag* out_result,
+                            size_t* out_index);
+
+  // SerializeUint128 writes |v| in little-endian form to |out|.
+  static void SerializeUint128(uint128 v, uint8* out);
+
+  // SerializeUint128 writes the first 96 bits of |v| in little-endian form
+  // to |out|.
+  static void SerializeUint128Short(uint128 v, uint8* out);
+
+  // Returns the name of the QuicRstStreamErrorCode as a char*
+  static const char* StreamErrorToString(QuicRstStreamErrorCode error);
+
+  // Returns the name of the QuicErrorCode as a char*
+  static const char* ErrorToString(QuicErrorCode error);
+
+  // Returns the level of encryption as a char*
+  static const char* EncryptionLevelToString(EncryptionLevel level);
+
+  // Returns TransmissionType as a char*
+  static const char* TransmissionTypeToString(TransmissionType type);
+
+  // TagToString is a utility function for pretty-printing handshake messages
+  // that converts a tag to a string. It will try to maintain the human friendly
+  // name if possible (i.e. kABCD -> "ABCD"), or will just treat it as a number
+  // if not.
+  static std::string TagToString(QuicTag tag);
+
+  // Given a binary buffer, return a hex+ASCII dump in the style of
+  // tcpdump's -X and -XX options:
+  // "0x0000:  0090 69bd 5400 000d 610f 0189 0800 4500  ..i.T...a.....E.\n"
+  // "0x0010:  001c fb98 4000 4001 7e18 d8ef 2301 455d  ....@.@.~...#.E]\n"
+  // "0x0020:  7fe2 0800 6bcb 0bc6 806e                 ....k....n\n"
+  static std::string StringToHexASCIIDump(base::StringPiece in_buffer);
+
+  static char* AsChars(unsigned char* data) {
+    return reinterpret_cast<char*>(data);
+  }
+
+  static QuicPriority LowestPriority();
+
+  static QuicPriority HighestPriority();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicUtils);
+};
+
+// Utility function that returns an IOVector object wrapped around |str|.
+inline IOVector MakeIOVector(base::StringPiece str) {
+  IOVector iov;
+  iov.Append(const_cast<char*>(str.data()), str.size());
+  return iov;
+}
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_UTILS_H_
diff --git a/net/quic/quic_utils_chromium.h b/net/quic/quic_utils_chromium.h
new file mode 100644
index 0000000..0229d1d
--- /dev/null
+++ b/net/quic/quic_utils_chromium.h
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Some helpers for quic that are for chromium codebase.
+
+#ifndef NET_QUIC_QUIC_UTILS_CHROMIUM_H_
+#define NET_QUIC_QUIC_UTILS_CHROMIUM_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace net {
+
+//
+// Find*()
+//
+
+// Returns a const reference to the value associated with the given key if it
+// exists. Crashes otherwise.
+//
+// This is intended as a replacement for operator[] as an rvalue (for reading)
+// when the key is guaranteed to exist.
+//
+// operator[] for lookup is discouraged for several reasons:
+//  * It has a side-effect of inserting missing keys
+//  * It is not thread-safe (even when it is not inserting, it can still
+//      choose to resize the underlying storage)
+//  * It invalidates iterators (when it chooses to resize)
+//  * It default constructs a value object even if it doesn't need to
+//
+// This version assumes the key is printable, and includes it in the fatal log
+// message.
+template <class Collection>
+const typename Collection::value_type::second_type&
+FindOrDie(const Collection& collection,
+          const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  CHECK(it != collection.end()) << "Map key not found: " << key;
+  return it->second;
+}
+
+// Same as above, but returns a non-const reference.
+template <class Collection>
+typename Collection::value_type::second_type&
+FindOrDie(Collection& collection,  // NOLINT
+          const typename Collection::value_type::first_type& key) {
+  typename Collection::iterator it = collection.find(key);
+  CHECK(it != collection.end()) << "Map key not found: " << key;
+  return it->second;
+}
+
+// Returns a pointer to the const value associated with the given key if it
+// exists, or NULL otherwise.
+template <class Collection>
+const typename Collection::value_type::second_type*
+FindOrNull(const Collection& collection,
+           const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return &it->second;
+}
+
+// Same as above but returns a pointer to the non-const value.
+template <class Collection>
+typename Collection::value_type::second_type*
+FindOrNull(Collection& collection,  // NOLINT
+           const typename Collection::value_type::first_type& key) {
+  typename Collection::iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return &it->second;
+}
+
+}  // namespace net
+
+#endif  // NET_QUIC_QUIC_UTILS_CHROMIUM_H_
diff --git a/net/quic/quic_utils_chromium_test.cc b/net/quic/quic_utils_chromium_test.cc
new file mode 100644
index 0000000..b9620cf
--- /dev/null
+++ b/net/quic/quic_utils_chromium_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_utils_chromium.h"
+
+#include <map>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::map;
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicUtilsChromiumTest, FindOrNullTest) {
+  map<int, int> m;
+  m[0] = 2;
+
+  // Check FindOrNull
+  int* p1 = FindOrNull(m, 0);
+  CHECK_EQ(*p1, 2);
+  ++(*p1);
+  const map<int, int>& const_m = m;
+  const int* p2 = FindOrNull(const_m, 0);
+  CHECK_EQ(*p2, 3);
+  CHECK(FindOrNull(m, 1) == nullptr);
+}
+
+TEST(QuicUtilsChromiumTest, FindOrDieTest) {
+  std::map<int, int> m;
+  m[10] = 15;
+  EXPECT_EQ(15, FindOrDie(m, 10));
+  // TODO(rtenneti): Use the latest DEATH macros after merging with latest rch's
+  // changes.
+  // ASSERT_DEATH(FindOrDie(m, 8), "Map key not found: 8");
+
+  // Make sure the non-const reference returning version works.
+  FindOrDie(m, 10) = 20;
+  EXPECT_EQ(20, FindOrDie(m, 10));
+
+  // Make sure we can lookup values in a const map.
+  const map<int, int>& const_m = m;
+  EXPECT_EQ(20, FindOrDie(const_m, 10));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_utils_test.cc b/net/quic/quic_utils_test.cc
new file mode 100644
index 0000000..0a0355f
--- /dev/null
+++ b/net/quic/quic_utils_test.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_utils.h"
+
+#include "net/quic/crypto/crypto_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+// A test string and a hex+ASCII dump of the same string.
+const unsigned char kString[] = {
+  0x00, 0x90, 0x69, 0xbd, 0x54, 0x00, 0x00, 0x0d,
+  0x61, 0x0f, 0x01, 0x89, 0x08, 0x00, 0x45, 0x00,
+  0x00, 0x1c, 0xfb, 0x98, 0x40, 0x00, 0x40, 0x01,
+  0x7e, 0x18, 0xd8, 0xef, 0x23, 0x01, 0x45, 0x5d,
+  0x7f, 0xe2, 0x08, 0x00, 0x6b, 0xcb, 0x0b, 0xc6,
+  0x80, 0x6e
+};
+
+const unsigned char kHexDump[] =
+"0x0000:  0090 69bd 5400 000d 610f 0189 0800 4500  ..i.T...a.....E.\n"
+"0x0010:  001c fb98 4000 4001 7e18 d8ef 2301 455d  ....@.@.~...#.E]\n"
+"0x0020:  7fe2 0800 6bcb 0bc6 806e                 ....k....n\n";
+
+TEST(QuicUtilsTest, StreamErrorToString) {
+  EXPECT_STREQ("QUIC_BAD_APPLICATION_PAYLOAD",
+               QuicUtils::StreamErrorToString(QUIC_BAD_APPLICATION_PAYLOAD));
+}
+
+TEST(QuicUtilsTest, ErrorToString) {
+  EXPECT_STREQ("QUIC_NO_ERROR",
+               QuicUtils::ErrorToString(QUIC_NO_ERROR));
+}
+
+TEST(QuicUtilsTest, StringToHexASCIIDumpArgTypes) {
+  // Verify that char*, string and StringPiece are all valid argument types.
+  struct {
+    const string input;
+    const string expected;
+  } tests[] = {
+    { "", "", },
+    { "A",   "0x0000:  41                                       A\n", },
+    { "AB",  "0x0000:  4142                                     AB\n", },
+    { "ABC", "0x0000:  4142 43                                  ABC\n", },
+    { "original",
+      "0x0000:  6f72 6967 696e 616c                      original\n", },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+    EXPECT_EQ(tests[i].expected,
+              QuicUtils::StringToHexASCIIDump(tests[i].input.c_str()));
+    EXPECT_EQ(tests[i].expected,
+              QuicUtils::StringToHexASCIIDump(tests[i].input));
+    EXPECT_EQ(tests[i].expected,
+              QuicUtils::StringToHexASCIIDump(StringPiece(tests[i].input)));
+  }
+}
+
+TEST(QuicUtilsTest, StringToHexASCIIDumpSuccess) {
+  EXPECT_EQ(string(reinterpret_cast<const char*>(kHexDump)),
+      QuicUtils::StringToHexASCIIDump(
+          string(reinterpret_cast<const char*>(kString), sizeof(kString))));
+}
+
+TEST(QuicUtilsTest, TagToString) {
+  EXPECT_EQ("SCFG",
+            QuicUtils::TagToString(kSCFG));
+  EXPECT_EQ("SNO ",
+            QuicUtils::TagToString(kServerNonceTag));
+  EXPECT_EQ("CRT ",
+            QuicUtils::TagToString(kCertificateTag));
+  EXPECT_EQ("CHLO",
+            QuicUtils::TagToString(MakeQuicTag('C', 'H', 'L', 'O')));
+  // A tag that contains a non-printing character will be printed as a decimal
+  // number.
+  EXPECT_EQ("525092931",
+            QuicUtils::TagToString(MakeQuicTag('C', 'H', 'L', '\x1f')));
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/quic_write_blocked_list.cc b/net/quic/quic_write_blocked_list.cc
new file mode 100644
index 0000000..10f9c34
--- /dev/null
+++ b/net/quic/quic_write_blocked_list.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/quic_write_blocked_list.h"
+
+namespace net {
+
+const QuicPriority QuicWriteBlockedList::kHighestPriority =
+    static_cast<QuicPriority>(net::kHighestPriority);
+const QuicPriority QuicWriteBlockedList::kLowestPriority =
+    static_cast<QuicPriority>(net::kLowestPriority);
+
+QuicWriteBlockedList::QuicWriteBlockedList()
+    : crypto_stream_blocked_(false),
+      headers_stream_blocked_(false) {}
+
+QuicWriteBlockedList::~QuicWriteBlockedList() {}
+
+}  // namespace net
diff --git a/net/quic/quic_write_blocked_list.h b/net/quic/quic_write_blocked_list.h
new file mode 100644
index 0000000..6727fb4
--- /dev/null
+++ b/net/quic/quic_write_blocked_list.h
@@ -0,0 +1,113 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+#ifndef NET_QUIC_QUIC_WRITE_BLOCKED_LIST_H_
+#define NET_QUIC_QUIC_WRITE_BLOCKED_LIST_H_
+
+#include <set>
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/spdy/write_blocked_list.h"
+
+namespace net {
+
+// Keeps tracks of the QUIC streams that have data to write, sorted by
+// priority.  QUIC stream priority order is:
+// Crypto stream > Headers stream > Data streams by requested priority.
+class NET_EXPORT_PRIVATE QuicWriteBlockedList {
+ private:
+  typedef WriteBlockedList<QuicStreamId> QuicWriteBlockedListBase;
+
+ public:
+  static const QuicPriority kHighestPriority;
+  static const QuicPriority kLowestPriority;
+
+  QuicWriteBlockedList();
+  ~QuicWriteBlockedList();
+
+  bool HasWriteBlockedDataStreams() const {
+    return base_write_blocked_list_.HasWriteBlockedStreams();
+  }
+
+  bool HasWriteBlockedCryptoOrHeadersStream() const {
+    return crypto_stream_blocked_ || headers_stream_blocked_;
+  }
+
+  size_t NumBlockedStreams() const {
+    size_t num_blocked = base_write_blocked_list_.NumBlockedStreams();
+    if (crypto_stream_blocked_) {
+      ++num_blocked;
+    }
+    if (headers_stream_blocked_) {
+      ++num_blocked;
+    }
+
+    return num_blocked;
+  }
+
+  QuicStreamId PopFront() {
+    if (crypto_stream_blocked_) {
+      crypto_stream_blocked_ = false;
+      return kCryptoStreamId;
+    }
+
+    if (headers_stream_blocked_) {
+      headers_stream_blocked_ = false;
+      return kHeadersStreamId;
+    }
+
+    SpdyPriority priority =
+        base_write_blocked_list_.GetHighestPriorityWriteBlockedList();
+    QuicStreamId id = base_write_blocked_list_.PopFront(priority);
+    blocked_streams_.erase(id);
+    return id;
+  }
+
+  void PushBack(QuicStreamId stream_id, QuicPriority priority) {
+    if (stream_id == kCryptoStreamId) {
+      DCHECK_EQ(kHighestPriority, priority);
+      // TODO(avd) Add DCHECK(!crypto_stream_blocked_)
+      crypto_stream_blocked_ = true;
+      return;
+    }
+
+    if (stream_id == kHeadersStreamId) {
+      DCHECK_EQ(kHighestPriority, priority);
+      // TODO(avd) Add DCHECK(!headers_stream_blocked_);
+      headers_stream_blocked_ = true;
+      return;
+    }
+
+    if (blocked_streams_.find(stream_id) != blocked_streams_.end()) {
+      DVLOG(1) << "Stream " << stream_id << " already in write blocked list.";
+      return;
+    }
+
+    base_write_blocked_list_.PushBack(
+        stream_id, static_cast<SpdyPriority>(priority));
+    blocked_streams_.insert(stream_id);
+    return;
+  }
+
+  bool crypto_stream_blocked() const { return crypto_stream_blocked_; }
+  bool headers_stream_blocked() const { return headers_stream_blocked_; }
+
+ private:
+  QuicWriteBlockedListBase base_write_blocked_list_;
+  bool crypto_stream_blocked_;
+  bool headers_stream_blocked_;
+
+  // Keep track of write blocked streams in a set for faster membership checking
+  // than iterating over the base_write_blocked_list_. The contents of this set
+  // should mirror the contents of base_write_blocked_list_.
+  std::set<QuicStreamId> blocked_streams_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicWriteBlockedList);
+};
+
+}  // namespace net
+
+
+#endif  // NET_QUIC_QUIC_WRITE_BLOCKED_LIST_H_
diff --git a/net/quic/quic_write_blocked_list_test.cc b/net/quic/quic_write_blocked_list_test.cc
new file mode 100644
index 0000000..0633f63
--- /dev/null
+++ b/net/quic/quic_write_blocked_list_test.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+#include "net/quic/quic_write_blocked_list.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicWriteBlockedListTest, PriorityOrder) {
+  QuicWriteBlockedList write_blocked_list;
+
+  // Mark streams blocked in roughly reverse priority order, and
+  // verify that streams are sorted.
+  write_blocked_list.PushBack(40,
+                              QuicWriteBlockedList::kLowestPriority);
+  write_blocked_list.PushBack(23,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(17,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(kHeadersStreamId,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(kCryptoStreamId,
+                              QuicWriteBlockedList::kHighestPriority);
+
+  EXPECT_EQ(5u, write_blocked_list.NumBlockedStreams());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedDataStreams());
+  // The Crypto stream is highest priority.
+  EXPECT_EQ(kCryptoStreamId, write_blocked_list.PopFront());
+  // Followed by the Headers stream.
+  EXPECT_EQ(kHeadersStreamId, write_blocked_list.PopFront());
+  // Streams with same priority are popped in the order they were inserted.
+  EXPECT_EQ(23u, write_blocked_list.PopFront());
+  EXPECT_EQ(17u, write_blocked_list.PopFront());
+  // Low priority stream appears last.
+  EXPECT_EQ(40u, write_blocked_list.PopFront());
+
+  EXPECT_EQ(0u, write_blocked_list.NumBlockedStreams());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedDataStreams());
+}
+
+TEST(QuicWriteBlockedListTest, CryptoStream) {
+  QuicWriteBlockedList write_blocked_list;
+  write_blocked_list.PushBack(kCryptoStreamId,
+                              QuicWriteBlockedList::kHighestPriority);
+
+  EXPECT_EQ(1u, write_blocked_list.NumBlockedStreams());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_EQ(kCryptoStreamId, write_blocked_list.PopFront());
+  EXPECT_EQ(0u, write_blocked_list.NumBlockedStreams());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+}
+
+TEST(QuicWriteBlockedListTest, HeadersStream) {
+  QuicWriteBlockedList write_blocked_list;
+  write_blocked_list.PushBack(kHeadersStreamId,
+                              QuicWriteBlockedList::kHighestPriority);
+
+  EXPECT_EQ(1u, write_blocked_list.NumBlockedStreams());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_EQ(kHeadersStreamId, write_blocked_list.PopFront());
+  EXPECT_EQ(0u, write_blocked_list.NumBlockedStreams());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+}
+
+TEST(QuicWriteBlockedListTest, VerifyHeadersStream) {
+  QuicWriteBlockedList write_blocked_list;
+  write_blocked_list.PushBack(5,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(kHeadersStreamId,
+                              QuicWriteBlockedList::kHighestPriority);
+
+  EXPECT_EQ(2u, write_blocked_list.NumBlockedStreams());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedDataStreams());
+  // In newer QUIC versions, there is a headers stream which is
+  // higher priority than data streams.
+  EXPECT_EQ(kHeadersStreamId, write_blocked_list.PopFront());
+  EXPECT_EQ(5u, write_blocked_list.PopFront());
+  EXPECT_EQ(0u, write_blocked_list.NumBlockedStreams());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedCryptoOrHeadersStream());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedDataStreams());
+}
+
+TEST(QuicWriteBlockedListTest, NoDuplicateEntries) {
+  // Test that QuicWriteBlockedList doesn't allow duplicate entries.
+  QuicWriteBlockedList write_blocked_list;
+
+  // Try to add a stream to the write blocked list multiple times at the same
+  // priority.
+  const QuicStreamId kBlockedId = kClientDataStreamId1;
+  write_blocked_list.PushBack(kBlockedId,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(kBlockedId,
+                              QuicWriteBlockedList::kHighestPriority);
+  write_blocked_list.PushBack(kBlockedId,
+                              QuicWriteBlockedList::kHighestPriority);
+
+  // This should only result in one blocked stream being added.
+  EXPECT_EQ(1u, write_blocked_list.NumBlockedStreams());
+  EXPECT_TRUE(write_blocked_list.HasWriteBlockedDataStreams());
+
+  // There should only be one stream to pop off the front.
+  EXPECT_EQ(kBlockedId, write_blocked_list.PopFront());
+  EXPECT_EQ(0u, write_blocked_list.NumBlockedStreams());
+  EXPECT_FALSE(write_blocked_list.HasWriteBlockedDataStreams());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/reliable_quic_stream.cc b/net/quic/reliable_quic_stream.cc
new file mode 100644
index 0000000..a10b0ab
--- /dev/null
+++ b/net/quic/reliable_quic_stream.cc
@@ -0,0 +1,544 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/reliable_quic_stream.h"
+
+#include "base/logging.h"
+#include "net/quic/iovector.h"
+#include "net/quic/quic_flow_controller.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_write_blocked_list.h"
+
+using base::StringPiece;
+using std::min;
+
+namespace net {
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+namespace {
+
+struct iovec MakeIovec(StringPiece data) {
+  struct iovec iov = {const_cast<char*>(data.data()),
+                      static_cast<size_t>(data.size())};
+  return iov;
+}
+
+size_t GetInitialStreamFlowControlWindowToSend(QuicSession* session) {
+  QuicVersion version = session->connection()->version();
+  if (version <= QUIC_VERSION_19) {
+    return session->config()->GetInitialFlowControlWindowToSend();
+  }
+
+  return session->config()->GetInitialStreamFlowControlWindowToSend();
+}
+
+size_t GetReceivedFlowControlWindow(QuicSession* session) {
+  QuicVersion version = session->connection()->version();
+  if (version <= QUIC_VERSION_19) {
+    if (session->config()->HasReceivedInitialFlowControlWindowBytes()) {
+      return session->config()->ReceivedInitialFlowControlWindowBytes();
+    }
+
+    return kDefaultFlowControlSendWindow;
+  }
+
+  // Version must be >= QUIC_VERSION_21, so we check for stream specific flow
+  // control window.
+  if (session->config()->HasReceivedInitialStreamFlowControlWindowBytes()) {
+    return session->config()->ReceivedInitialStreamFlowControlWindowBytes();
+  }
+
+  return kDefaultFlowControlSendWindow;
+}
+
+}  // namespace
+
+// Wrapper that aggregates OnAckNotifications for packets sent using
+// WriteOrBufferData and delivers them to the original
+// QuicAckNotifier::DelegateInterface after all bytes written using
+// WriteOrBufferData are acked.  This level of indirection is
+// necessary because the delegate interface provides no mechanism that
+// WriteOrBufferData can use to inform it that the write required
+// multiple WritevData calls or that only part of the data has been
+// sent out by the time ACKs start arriving.
+class ReliableQuicStream::ProxyAckNotifierDelegate
+    : public QuicAckNotifier::DelegateInterface {
+ public:
+  explicit ProxyAckNotifierDelegate(DelegateInterface* delegate)
+      : delegate_(delegate),
+        pending_acks_(0),
+        wrote_last_data_(false),
+        num_original_packets_(0),
+        num_original_bytes_(0),
+        num_retransmitted_packets_(0),
+        num_retransmitted_bytes_(0) {
+  }
+
+  virtual void OnAckNotification(int num_original_packets,
+                                 int num_original_bytes,
+                                 int num_retransmitted_packets,
+                                 int num_retransmitted_bytes,
+                                 QuicTime::Delta delta_largest_observed)
+      OVERRIDE {
+    DCHECK_LT(0, pending_acks_);
+    --pending_acks_;
+    num_original_packets_ += num_original_packets;
+    num_original_bytes_ += num_original_bytes;
+    num_retransmitted_packets_ += num_retransmitted_packets;
+    num_retransmitted_bytes_ += num_retransmitted_bytes;
+
+    if (wrote_last_data_ && pending_acks_ == 0) {
+      delegate_->OnAckNotification(num_original_packets_,
+                                   num_original_bytes_,
+                                   num_retransmitted_packets_,
+                                   num_retransmitted_bytes_,
+                                   delta_largest_observed);
+    }
+  }
+
+  void WroteData(bool last_data) {
+    DCHECK(!wrote_last_data_);
+    ++pending_acks_;
+    wrote_last_data_ = last_data;
+  }
+
+ protected:
+  // Delegates are ref counted.
+  virtual ~ProxyAckNotifierDelegate() OVERRIDE {
+  }
+
+ private:
+  // Original delegate.  delegate_->OnAckNotification will be called when:
+  //   wrote_last_data_ == true and pending_acks_ == 0
+  scoped_refptr<DelegateInterface> delegate_;
+
+  // Number of outstanding acks.
+  int pending_acks_;
+
+  // True if no pending writes remain.
+  bool wrote_last_data_;
+
+  // Accumulators.
+  int num_original_packets_;
+  int num_original_bytes_;
+  int num_retransmitted_packets_;
+  int num_retransmitted_bytes_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyAckNotifierDelegate);
+};
+
+ReliableQuicStream::PendingData::PendingData(
+    string data_in, scoped_refptr<ProxyAckNotifierDelegate> delegate_in)
+    : data(data_in), delegate(delegate_in) {
+}
+
+ReliableQuicStream::PendingData::~PendingData() {
+}
+
+ReliableQuicStream::ReliableQuicStream(QuicStreamId id, QuicSession* session)
+    : sequencer_(this),
+      id_(id),
+      session_(session),
+      stream_bytes_read_(0),
+      stream_bytes_written_(0),
+      stream_error_(QUIC_STREAM_NO_ERROR),
+      connection_error_(QUIC_NO_ERROR),
+      read_side_closed_(false),
+      write_side_closed_(false),
+      fin_buffered_(false),
+      fin_sent_(false),
+      fin_received_(false),
+      rst_sent_(false),
+      rst_received_(false),
+      fec_policy_(FEC_PROTECT_OPTIONAL),
+      is_server_(session_->is_server()),
+      flow_controller_(
+          session_->connection(), id_, is_server_,
+          GetReceivedFlowControlWindow(session),
+          GetInitialStreamFlowControlWindowToSend(session),
+          GetInitialStreamFlowControlWindowToSend(session)),
+      connection_flow_controller_(session_->flow_controller()),
+      stream_contributes_to_connection_flow_control_(true) {
+}
+
+ReliableQuicStream::~ReliableQuicStream() {
+}
+
+void ReliableQuicStream::OnStreamFrame(const QuicStreamFrame& frame) {
+  if (read_side_closed_) {
+    DVLOG(1) << ENDPOINT << "Ignoring frame " << frame.stream_id;
+    // We don't want to be reading: blackhole the data.
+    return;
+  }
+
+  if (frame.stream_id != id_) {
+    session_->connection()->SendConnectionClose(QUIC_INTERNAL_ERROR);
+    return;
+  }
+
+  if (frame.fin) {
+    fin_received_ = true;
+  }
+
+  // This count include duplicate data received.
+  size_t frame_payload_size = frame.data.TotalBufferSize();
+  stream_bytes_read_ += frame_payload_size;
+
+  // Flow control is interested in tracking highest received offset.
+  if (MaybeIncreaseHighestReceivedOffset(frame.offset + frame_payload_size)) {
+    // As the highest received offset has changed, we should check to see if
+    // this is a violation of flow control.
+    if (flow_controller_.FlowControlViolation() ||
+        connection_flow_controller_->FlowControlViolation()) {
+      session_->connection()->SendConnectionClose(
+          QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA);
+      return;
+    }
+  }
+
+  sequencer_.OnStreamFrame(frame);
+}
+
+int ReliableQuicStream::num_frames_received() const {
+  return sequencer_.num_frames_received();
+}
+
+int ReliableQuicStream::num_duplicate_frames_received() const {
+  return sequencer_.num_duplicate_frames_received();
+}
+
+void ReliableQuicStream::OnStreamReset(const QuicRstStreamFrame& frame) {
+  rst_received_ = true;
+  MaybeIncreaseHighestReceivedOffset(frame.byte_offset);
+
+  stream_error_ = frame.error_code;
+  CloseWriteSide();
+  CloseReadSide();
+}
+
+void ReliableQuicStream::OnConnectionClosed(QuicErrorCode error,
+                                            bool from_peer) {
+  if (read_side_closed_ && write_side_closed_) {
+    return;
+  }
+  if (error != QUIC_NO_ERROR) {
+    stream_error_ = QUIC_STREAM_CONNECTION_ERROR;
+    connection_error_ = error;
+  }
+
+  CloseWriteSide();
+  CloseReadSide();
+}
+
+void ReliableQuicStream::OnFinRead() {
+  DCHECK(sequencer_.IsClosed());
+  fin_received_ = true;
+  CloseReadSide();
+}
+
+void ReliableQuicStream::Reset(QuicRstStreamErrorCode error) {
+  DCHECK_NE(QUIC_STREAM_NO_ERROR, error);
+  stream_error_ = error;
+  // Sending a RstStream results in calling CloseStream.
+  session()->SendRstStream(id(), error, stream_bytes_written_);
+  rst_sent_ = true;
+}
+
+void ReliableQuicStream::CloseConnection(QuicErrorCode error) {
+  session()->connection()->SendConnectionClose(error);
+}
+
+void ReliableQuicStream::CloseConnectionWithDetails(QuicErrorCode error,
+                                                    const string& details) {
+  session()->connection()->SendConnectionCloseWithDetails(error, details);
+}
+
+QuicVersion ReliableQuicStream::version() const {
+  return session()->connection()->version();
+}
+
+void ReliableQuicStream::WriteOrBufferData(
+    StringPiece data,
+    bool fin,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  if (data.empty() && !fin) {
+    LOG(DFATAL) << "data.empty() && !fin";
+    return;
+  }
+
+  if (fin_buffered_) {
+    LOG(DFATAL) << "Fin already buffered";
+    return;
+  }
+
+  scoped_refptr<ProxyAckNotifierDelegate> proxy_delegate;
+  if (ack_notifier_delegate != nullptr) {
+    proxy_delegate = new ProxyAckNotifierDelegate(ack_notifier_delegate);
+  }
+
+  QuicConsumedData consumed_data(0, false);
+  fin_buffered_ = fin;
+
+  if (queued_data_.empty()) {
+    struct iovec iov(MakeIovec(data));
+    consumed_data = WritevData(&iov, 1, fin, proxy_delegate.get());
+    DCHECK_LE(consumed_data.bytes_consumed, data.length());
+  }
+
+  bool write_completed;
+  // If there's unconsumed data or an unconsumed fin, queue it.
+  if (consumed_data.bytes_consumed < data.length() ||
+      (fin && !consumed_data.fin_consumed)) {
+    StringPiece remainder(data.substr(consumed_data.bytes_consumed));
+    queued_data_.push_back(PendingData(remainder.as_string(), proxy_delegate));
+    write_completed = false;
+  } else {
+    write_completed = true;
+  }
+
+  if ((proxy_delegate.get() != nullptr) &&
+      (consumed_data.bytes_consumed > 0 || consumed_data.fin_consumed)) {
+    proxy_delegate->WroteData(write_completed);
+  }
+}
+
+void ReliableQuicStream::OnCanWrite() {
+  bool fin = false;
+  while (!queued_data_.empty()) {
+    PendingData* pending_data = &queued_data_.front();
+    ProxyAckNotifierDelegate* delegate = pending_data->delegate.get();
+    if (queued_data_.size() == 1 && fin_buffered_) {
+      fin = true;
+    }
+    struct iovec iov(MakeIovec(pending_data->data));
+    QuicConsumedData consumed_data = WritevData(&iov, 1, fin, delegate);
+    if (consumed_data.bytes_consumed == pending_data->data.size() &&
+        fin == consumed_data.fin_consumed) {
+      queued_data_.pop_front();
+      if (delegate != nullptr) {
+        delegate->WroteData(true);
+      }
+    } else {
+      if (consumed_data.bytes_consumed > 0) {
+        pending_data->data.erase(0, consumed_data.bytes_consumed);
+        if (delegate != nullptr) {
+          delegate->WroteData(false);
+        }
+      }
+      break;
+    }
+  }
+}
+
+void ReliableQuicStream::MaybeSendBlocked() {
+  flow_controller_.MaybeSendBlocked();
+  if (!stream_contributes_to_connection_flow_control_) {
+    return;
+  }
+  connection_flow_controller_->MaybeSendBlocked();
+  // If we are connection level flow control blocked, then add the stream
+  // to the write blocked list. It will be given a chance to write when a
+  // connection level WINDOW_UPDATE arrives.
+  if (connection_flow_controller_->IsBlocked() &&
+      !flow_controller_.IsBlocked()) {
+    session_->MarkWriteBlocked(id(), EffectivePriority());
+  }
+}
+
+QuicConsumedData ReliableQuicStream::WritevData(
+    const struct iovec* iov,
+    int iov_count,
+    bool fin,
+    QuicAckNotifier::DelegateInterface* ack_notifier_delegate) {
+  if (write_side_closed_) {
+    DLOG(ERROR) << ENDPOINT << "Attempt to write when the write side is closed";
+    return QuicConsumedData(0, false);
+  }
+
+  // How much data we want to write.
+  size_t write_length = TotalIovecLength(iov, iov_count);
+
+  // A FIN with zero data payload should not be flow control blocked.
+  bool fin_with_zero_data = (fin && write_length == 0);
+
+  if (flow_controller_.IsEnabled()) {
+    // How much data we are allowed to write from flow control.
+    uint64 send_window = flow_controller_.SendWindowSize();
+    // TODO(rjshade): Remove connection_flow_controller_->IsEnabled() check when
+    // removing QUIC_VERSION_19.
+    if (stream_contributes_to_connection_flow_control_ &&
+        connection_flow_controller_->IsEnabled()) {
+      send_window =
+          min(send_window, connection_flow_controller_->SendWindowSize());
+    }
+
+    if (send_window == 0 && !fin_with_zero_data) {
+      // Quick return if we can't send anything.
+      MaybeSendBlocked();
+      return QuicConsumedData(0, false);
+    }
+
+    if (write_length > send_window) {
+      // Don't send the FIN if we aren't going to send all the data.
+      fin = false;
+
+      // Writing more data would be a violation of flow control.
+      write_length = send_window;
+    }
+  }
+
+  // Fill an IOVector with bytes from the iovec.
+  IOVector data;
+  data.AppendIovecAtMostBytes(iov, iov_count, write_length);
+
+  QuicConsumedData consumed_data = session()->WritevData(
+      id(), data, stream_bytes_written_, fin, GetFecProtection(),
+      ack_notifier_delegate);
+  stream_bytes_written_ += consumed_data.bytes_consumed;
+
+  AddBytesSent(consumed_data.bytes_consumed);
+
+  if (consumed_data.bytes_consumed == write_length) {
+    if (!fin_with_zero_data) {
+      MaybeSendBlocked();
+    }
+    if (fin && consumed_data.fin_consumed) {
+      fin_sent_ = true;
+      CloseWriteSide();
+    } else if (fin && !consumed_data.fin_consumed) {
+      session_->MarkWriteBlocked(id(), EffectivePriority());
+    }
+  } else {
+    session_->MarkWriteBlocked(id(), EffectivePriority());
+  }
+  return consumed_data;
+}
+
+FecProtection ReliableQuicStream::GetFecProtection() {
+  return fec_policy_ == FEC_PROTECT_ALWAYS ? MUST_FEC_PROTECT : MAY_FEC_PROTECT;
+}
+
+void ReliableQuicStream::CloseReadSide() {
+  if (read_side_closed_) {
+    return;
+  }
+  DVLOG(1) << ENDPOINT << "Done reading from stream " << id();
+
+  read_side_closed_ = true;
+  if (write_side_closed_) {
+    DVLOG(1) << ENDPOINT << "Closing stream: " << id();
+    session_->CloseStream(id());
+  }
+}
+
+void ReliableQuicStream::CloseWriteSide() {
+  if (write_side_closed_) {
+    return;
+  }
+  DVLOG(1) << ENDPOINT << "Done writing to stream " << id();
+
+  write_side_closed_ = true;
+  if (read_side_closed_) {
+    DVLOG(1) << ENDPOINT << "Closing stream: " << id();
+    session_->CloseStream(id());
+  }
+}
+
+bool ReliableQuicStream::HasBufferedData() const {
+  return !queued_data_.empty();
+}
+
+void ReliableQuicStream::OnClose() {
+  CloseReadSide();
+  CloseWriteSide();
+
+  if (!fin_sent_ && !rst_sent_) {
+    // For flow control accounting, we must tell the peer how many bytes we have
+    // written on this stream before termination. Done here if needed, using a
+    // RST frame.
+    DVLOG(1) << ENDPOINT << "Sending RST in OnClose: " << id();
+    session_->SendRstStream(id(), QUIC_RST_FLOW_CONTROL_ACCOUNTING,
+                            stream_bytes_written_);
+    rst_sent_ = true;
+  }
+
+  // We are closing the stream and will not process any further incoming bytes.
+  // As there may be more bytes in flight and we need to ensure that both
+  // endpoints have the same connection level flow control state, mark all
+  // unreceived or buffered bytes as consumed.
+  uint64 bytes_to_consume = flow_controller_.highest_received_byte_offset() -
+      flow_controller_.bytes_consumed();
+  AddBytesConsumed(bytes_to_consume);
+}
+
+void ReliableQuicStream::OnWindowUpdateFrame(
+    const QuicWindowUpdateFrame& frame) {
+  if (!flow_controller_.IsEnabled()) {
+    DLOG(DFATAL) << "Flow control not enabled! " << version();
+    return;
+  }
+
+  if (flow_controller_.UpdateSendWindowOffset(frame.byte_offset)) {
+    // We can write again!
+    // TODO(rjshade): This does not respect priorities (e.g. multiple
+    //                outstanding POSTs are unblocked on arrival of
+    //                SHLO with initial window).
+    // As long as the connection is not flow control blocked, we can write!
+    OnCanWrite();
+  }
+}
+
+bool ReliableQuicStream::MaybeIncreaseHighestReceivedOffset(uint64 new_offset) {
+  if (!flow_controller_.IsEnabled()) {
+    return false;
+  }
+  uint64 increment =
+      new_offset - flow_controller_.highest_received_byte_offset();
+  if (!flow_controller_.UpdateHighestReceivedOffset(new_offset)) {
+    return false;
+  }
+
+  // If |new_offset| increased the stream flow controller's highest received
+  // offset, then we need to increase the connection flow controller's value
+  // by the incremental difference.
+  if (stream_contributes_to_connection_flow_control_) {
+    connection_flow_controller_->UpdateHighestReceivedOffset(
+        connection_flow_controller_->highest_received_byte_offset() +
+        increment);
+  }
+  return true;
+}
+
+void ReliableQuicStream::AddBytesSent(uint64 bytes) {
+  if (flow_controller_.IsEnabled()) {
+    flow_controller_.AddBytesSent(bytes);
+    if (stream_contributes_to_connection_flow_control_) {
+      connection_flow_controller_->AddBytesSent(bytes);
+    }
+  }
+}
+
+void ReliableQuicStream::AddBytesConsumed(uint64 bytes) {
+  if (flow_controller_.IsEnabled()) {
+    // Only adjust stream level flow controller if we are still reading.
+    if (!read_side_closed_) {
+      flow_controller_.AddBytesConsumed(bytes);
+    }
+
+    if (stream_contributes_to_connection_flow_control_) {
+      connection_flow_controller_->AddBytesConsumed(bytes);
+    }
+  }
+}
+
+bool ReliableQuicStream::IsFlowControlBlocked() {
+  if (flow_controller_.IsBlocked()) {
+    return true;
+  }
+  return stream_contributes_to_connection_flow_control_ &&
+      connection_flow_controller_->IsBlocked();
+}
+
+}  // namespace net
diff --git a/net/quic/reliable_quic_stream.h b/net/quic/reliable_quic_stream.h
new file mode 100644
index 0000000..5b5ae0f
--- /dev/null
+++ b/net/quic/reliable_quic_stream.h
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The base class for client/server reliable streams.
+
+#ifndef NET_QUIC_RELIABLE_QUIC_STREAM_H_
+#define NET_QUIC_RELIABLE_QUIC_STREAM_H_
+
+#include <sys/types.h>
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/base/iovec.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_flow_controller.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_stream_sequencer.h"
+#include "net/quic/quic_types.h"
+
+namespace net {
+
+namespace test {
+class ReliableQuicStreamPeer;
+}  // namespace test
+
+class QuicSession;
+
+class NET_EXPORT_PRIVATE ReliableQuicStream {
+ public:
+  ReliableQuicStream(QuicStreamId id,
+                     QuicSession* session);
+
+  virtual ~ReliableQuicStream();
+
+  // Called when a (potentially duplicate) stream frame has been received
+  // for this stream.
+  virtual void OnStreamFrame(const QuicStreamFrame& frame);
+
+  // Called when the connection becomes writeable to allow the stream
+  // to write any pending data.
+  virtual void OnCanWrite();
+
+  // Called by the session just before the stream is deleted.
+  virtual void OnClose();
+
+  // Called when we get a stream reset from the peer.
+  virtual void OnStreamReset(const QuicRstStreamFrame& frame);
+
+  // Called when we get or send a connection close, and should immediately
+  // close the stream.  This is not passed through the sequencer,
+  // but is handled immediately.
+  virtual void OnConnectionClosed(QuicErrorCode error, bool from_peer);
+
+  // Called when the final data has been read.
+  virtual void OnFinRead();
+
+  virtual uint32 ProcessRawData(const char* data, uint32 data_len) = 0;
+
+  // Called to reset the stream from this end.
+  virtual void Reset(QuicRstStreamErrorCode error);
+
+  // Called to close the entire connection from this end.
+  virtual void CloseConnection(QuicErrorCode error);
+  virtual void CloseConnectionWithDetails(QuicErrorCode error,
+                                          const string& details);
+
+  // Returns the effective priority for the stream.  This value may change
+  // during the life of the stream.
+  virtual QuicPriority EffectivePriority() const = 0;
+
+  QuicStreamId id() const { return id_; }
+
+  QuicRstStreamErrorCode stream_error() const { return stream_error_; }
+  QuicErrorCode connection_error() const { return connection_error_; }
+
+  bool read_side_closed() const { return read_side_closed_; }
+  bool write_side_closed() const { return write_side_closed_; }
+
+  uint64 stream_bytes_read() const { return stream_bytes_read_; }
+  uint64 stream_bytes_written() const { return stream_bytes_written_; }
+
+  QuicVersion version() const;
+
+  void set_fin_sent(bool fin_sent) { fin_sent_ = fin_sent; }
+  void set_rst_sent(bool rst_sent) { rst_sent_ = rst_sent; }
+
+  void set_fec_policy(FecPolicy fec_policy) { fec_policy_ = fec_policy; }
+  FecPolicy fec_policy() const { return fec_policy_; }
+
+  // Adjust our flow control windows according to new offset in |frame|.
+  virtual void OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame);
+
+  int num_frames_received() const;
+
+  int num_duplicate_frames_received() const;
+
+  QuicFlowController* flow_controller() { return &flow_controller_; }
+
+  // Called when we see a frame which could increase the highest offset.
+  // Returns true if the highest offset did increase.
+  bool MaybeIncreaseHighestReceivedOffset(uint64 new_offset);
+  // Called when bytese are sent to the peer.
+  void AddBytesSent(uint64 bytes);
+  // Called by the stream sequencer as bytes are consumed from the buffer.
+  // If our receive window has dropped below the threshold, then send a
+  // WINDOW_UPDATE frame.
+  void AddBytesConsumed(uint64 bytes);
+
+  // Returns true if the stream is flow control blocked, by the stream flow
+  // control window or the connection flow control window.
+  bool IsFlowControlBlocked();
+
+  // Returns true if we have received either a RST or a FIN - either of which
+  // gives a definitive number of bytes which the peer has sent. If this is not
+  // true on stream termination the session must keep track of the stream's byte
+  // offset until a definitive final value arrives.
+  bool HasFinalReceivedByteOffset() const {
+    return fin_received_ || rst_received_;
+  }
+
+ protected:
+  // Sends as much of 'data' to the connection as the connection will consume,
+  // and then buffers any remaining data in queued_data_.
+  void WriteOrBufferData(
+      base::StringPiece data,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // Sends as many bytes in the first |count| buffers of |iov| to the connection
+  // as the connection will consume.
+  // If |ack_notifier_delegate| is provided, then it will be notified once all
+  // the ACKs for this write have been received.
+  // Returns the number of bytes consumed by the connection.
+  QuicConsumedData WritevData(
+      const struct iovec* iov,
+      int iov_count,
+      bool fin,
+      QuicAckNotifier::DelegateInterface* ack_notifier_delegate);
+
+  // Helper method that returns FecProtection to use for writes to the session.
+  FecProtection GetFecProtection();
+
+  // Close the read side of the socket.  Further frames will not be accepted.
+  virtual void CloseReadSide();
+
+  // Close the write side of the socket.  Further writes will fail.
+  void CloseWriteSide();
+
+  bool HasBufferedData() const;
+
+  bool fin_buffered() const { return fin_buffered_; }
+
+  const QuicSession* session() const { return session_; }
+  QuicSession* session() { return session_; }
+
+  const QuicStreamSequencer* sequencer() const { return &sequencer_; }
+  QuicStreamSequencer* sequencer() { return &sequencer_; }
+
+  // TODO(rjshade): Remove this method when removing QUIC_VERSION_19.
+  void DisableFlowControl() {
+    flow_controller_.Disable();
+  }
+
+  void DisableConnectionFlowControlForThisStream() {
+    stream_contributes_to_connection_flow_control_ = false;
+  }
+
+ private:
+  friend class test::ReliableQuicStreamPeer;
+  friend class QuicStreamUtils;
+  class ProxyAckNotifierDelegate;
+
+  struct PendingData {
+    PendingData(string data_in,
+                scoped_refptr<ProxyAckNotifierDelegate> delegate_in);
+    ~PendingData();
+
+    string data;
+    // Delegate that should be notified when the pending data is acked.
+    // Can be nullptr.
+    scoped_refptr<ProxyAckNotifierDelegate> delegate;
+  };
+
+  // Calls MaybeSendBlocked on our flow controller, and connection level flow
+  // controller. If we are flow control blocked, marks this stream as write
+  // blocked.
+  void MaybeSendBlocked();
+
+  std::list<PendingData> queued_data_;
+
+  QuicStreamSequencer sequencer_;
+  QuicStreamId id_;
+  QuicSession* session_;
+  // Bytes read and written refer to payload bytes only: they do not include
+  // framing, encryption overhead etc.
+  uint64 stream_bytes_read_;
+  uint64 stream_bytes_written_;
+
+  // Stream error code received from a RstStreamFrame or error code sent by the
+  // visitor or sequencer in the RstStreamFrame.
+  QuicRstStreamErrorCode stream_error_;
+  // Connection error code due to which the stream was closed. |stream_error_|
+  // is set to |QUIC_STREAM_CONNECTION_ERROR| when this happens and consumers
+  // should check |connection_error_|.
+  QuicErrorCode connection_error_;
+
+  // True if the read side is closed and further frames should be rejected.
+  bool read_side_closed_;
+  // True if the write side is closed, and further writes should fail.
+  bool write_side_closed_;
+
+  bool fin_buffered_;
+  bool fin_sent_;
+
+  // True if this stream has received (and the sequencer has accepted) a
+  // StreamFrame with the FIN set.
+  bool fin_received_;
+
+  // In combination with fin_sent_, used to ensure that a FIN and/or a RST is
+  // always sent before stream termination.
+  bool rst_sent_;
+
+  // True if this stream has received a RST stream frame.
+  bool rst_received_;
+
+  // FEC policy to be used for this stream.
+  FecPolicy fec_policy_;
+
+  // True if the session this stream is running under is a server session.
+  bool is_server_;
+
+  QuicFlowController flow_controller_;
+
+  // The connection level flow controller. Not owned.
+  QuicFlowController* connection_flow_controller_;
+
+  // Special streams, such as the crypto and headers streams, do not respect
+  // connection level flow control limits (but are stream level flow control
+  // limited).
+  bool stream_contributes_to_connection_flow_control_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReliableQuicStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_RELIABLE_QUIC_STREAM_H_
diff --git a/net/quic/reliable_quic_stream_test.cc b/net/quic/reliable_quic_stream_test.cc
new file mode 100644
index 0000000..336ea53
--- /dev/null
+++ b/net/quic/reliable_quic_stream_test.cc
@@ -0,0 +1,652 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/reliable_quic_stream.h"
+
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/quic_write_blocked_list.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_config_peer.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_flow_controller_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+
+using base::StringPiece;
+using std::min;
+using testing::CreateFunctor;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrictMock;
+using testing::WithArgs;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kData1[] = "FooAndBar";
+const char kData2[] = "EepAndBaz";
+const size_t kDataLen = 9;
+const bool kIsServer = true;
+const bool kShouldProcessData = true;
+
+class TestStream : public ReliableQuicStream {
+ public:
+  TestStream(QuicStreamId id,
+             QuicSession* session,
+             bool should_process_data)
+      : ReliableQuicStream(id, session),
+        should_process_data_(should_process_data) {}
+
+  virtual uint32 ProcessRawData(const char* data, uint32 data_len) OVERRIDE {
+    EXPECT_NE(0u, data_len);
+    DVLOG(1) << "ProcessData data_len: " << data_len;
+    data_ += string(data, data_len);
+    return should_process_data_ ? data_len : 0;
+  }
+
+  virtual QuicPriority EffectivePriority() const OVERRIDE {
+    return QuicUtils::HighestPriority();
+  }
+
+  using ReliableQuicStream::WriteOrBufferData;
+  using ReliableQuicStream::CloseReadSide;
+  using ReliableQuicStream::CloseWriteSide;
+  using ReliableQuicStream::OnClose;
+
+ private:
+  bool should_process_data_;
+  string data_;
+};
+
+class ReliableQuicStreamTest : public ::testing::TestWithParam<bool> {
+ public:
+  ReliableQuicStreamTest()
+      : initial_flow_control_window_bytes_(kMaxPacketSize),
+        zero_(QuicTime::Delta::Zero()),
+        supported_versions_(QuicSupportedVersions()) {
+    headers_[":host"] = "www.google.com";
+    headers_[":path"] = "/index.hml";
+    headers_[":scheme"] = "https";
+    headers_["cookie"] =
+        "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+        "__utmc=160408618; "
+        "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+        "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+        "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+        "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+        "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+        "1zFMi5vzcns38-8_Sns; "
+        "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+        "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+        "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+        "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+        "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+        "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+        "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+        "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+        "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+        "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+        "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+        "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+        "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+        "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+        "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+  }
+
+  void set_supported_versions(const QuicVersionVector& versions) {
+    supported_versions_ = versions;
+  }
+
+  void Initialize(bool stream_should_process_data) {
+    connection_ =
+        new StrictMock<MockConnection>(kIsServer, supported_versions_);
+    session_.reset(new StrictMock<MockSession>(connection_));
+
+    // New streams rely on having the peer's flow control receive window
+    // negotiated in the config.
+    QuicConfigPeer::SetReceivedInitialFlowControlWindow(
+        session_->config(), initial_flow_control_window_bytes_);
+    QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(
+        session_->config(), initial_flow_control_window_bytes_);
+
+    stream_.reset(new TestStream(kHeadersStreamId, session_.get(),
+                                 stream_should_process_data));
+    write_blocked_list_ =
+        QuicSessionPeer::GetWriteBlockedStreams(session_.get());
+  }
+
+  bool fin_sent() { return ReliableQuicStreamPeer::FinSent(stream_.get()); }
+  bool rst_sent() { return ReliableQuicStreamPeer::RstSent(stream_.get()); }
+
+  void set_initial_flow_control_window_bytes(uint32 val) {
+    initial_flow_control_window_bytes_ = val;
+  }
+
+  bool HasWriteBlockedStreams() {
+    return write_blocked_list_->HasWriteBlockedCryptoOrHeadersStream() ||
+           write_blocked_list_->HasWriteBlockedDataStreams();
+  }
+
+ protected:
+  MockConnection* connection_;
+  scoped_ptr<MockSession> session_;
+  scoped_ptr<TestStream> stream_;
+  SpdyHeaderBlock headers_;
+  QuicWriteBlockedList* write_blocked_list_;
+  uint32 initial_flow_control_window_bytes_;
+  QuicTime::Delta zero_;
+  QuicVersionVector supported_versions_;
+};
+
+TEST_F(ReliableQuicStreamTest, WriteAllData) {
+  Initialize(kShouldProcessData);
+
+  size_t length = 1 + QuicPacketCreator::StreamFramePacketOverhead(
+      PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+      PACKET_6BYTE_SEQUENCE_NUMBER, 0u, NOT_IN_FEC_GROUP);
+  QuicConnectionPeer::GetPacketCreator(connection_)->set_max_packet_length(
+      length);
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen, true)));
+  stream_->WriteOrBufferData(kData1, false, nullptr);
+  EXPECT_FALSE(HasWriteBlockedStreams());
+}
+
+TEST_F(ReliableQuicStreamTest, NoBlockingIfNoDataOrFin) {
+  Initialize(kShouldProcessData);
+
+  // Write no data and no fin.  If we consume nothing we should not be write
+  // blocked.
+  EXPECT_DFATAL(stream_->WriteOrBufferData(StringPiece(), false, nullptr), "");
+  EXPECT_FALSE(HasWriteBlockedStreams());
+}
+
+TEST_F(ReliableQuicStreamTest, BlockIfOnlySomeDataConsumed) {
+  Initialize(kShouldProcessData);
+
+  // Write some data and no fin.  If we consume some but not all of the data,
+  // we should be write blocked a not all the data was consumed.
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(1, false)));
+  stream_->WriteOrBufferData(StringPiece(kData1, 2), false, nullptr);
+  ASSERT_EQ(1u, write_blocked_list_->NumBlockedStreams());
+}
+
+TEST_F(ReliableQuicStreamTest, BlockIfFinNotConsumedWithData) {
+  Initialize(kShouldProcessData);
+
+  // Write some data and no fin.  If we consume all the data but not the fin,
+  // we should be write blocked because the fin was not consumed.
+  // (This should never actually happen as the fin should be sent out with the
+  // last data)
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(2, false)));
+  stream_->WriteOrBufferData(StringPiece(kData1, 2), true, nullptr);
+  ASSERT_EQ(1u, write_blocked_list_->NumBlockedStreams());
+}
+
+TEST_F(ReliableQuicStreamTest, BlockIfSoloFinNotConsumed) {
+  Initialize(kShouldProcessData);
+
+  // Write no data and a fin.  If we consume nothing we should be write blocked,
+  // as the fin was not consumed.
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(0, false)));
+  stream_->WriteOrBufferData(StringPiece(), true, nullptr);
+  ASSERT_EQ(1u, write_blocked_list_->NumBlockedStreams());
+}
+
+TEST_F(ReliableQuicStreamTest, WriteOrBufferData) {
+  Initialize(kShouldProcessData);
+
+  EXPECT_FALSE(HasWriteBlockedStreams());
+  size_t length = 1 + QuicPacketCreator::StreamFramePacketOverhead(
+      PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+      PACKET_6BYTE_SEQUENCE_NUMBER, 0u, NOT_IN_FEC_GROUP);
+  QuicConnectionPeer::GetPacketCreator(connection_)->set_max_packet_length(
+      length);
+
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen - 1, false)));
+  stream_->WriteOrBufferData(kData1, false, nullptr);
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  // Queue a bytes_consumed write.
+  stream_->WriteOrBufferData(kData2, false, nullptr);
+
+  // Make sure we get the tail of the first write followed by the bytes_consumed
+  InSequence s;
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).
+      WillOnce(Return(QuicConsumedData(1, false)));
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).
+      WillOnce(Return(QuicConsumedData(kDataLen - 2, false)));
+  stream_->OnCanWrite();
+
+  // And finally the end of the bytes_consumed.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _)).
+      WillOnce(Return(QuicConsumedData(2, true)));
+  stream_->OnCanWrite();
+}
+
+TEST_F(ReliableQuicStreamTest, WriteOrBufferDataWithFecProtectAlways) {
+  Initialize(kShouldProcessData);
+
+  // Set FEC policy on stream.
+  ReliableQuicStreamPeer::SetFecPolicy(stream_.get(), FEC_PROTECT_ALWAYS);
+
+  EXPECT_FALSE(HasWriteBlockedStreams());
+  size_t length = 1 + QuicPacketCreator::StreamFramePacketOverhead(
+      PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+      PACKET_6BYTE_SEQUENCE_NUMBER, 0u, IN_FEC_GROUP);
+  QuicConnectionPeer::GetPacketCreator(connection_)->set_max_packet_length(
+      length);
+
+  // Write first data onto stream, which will cause one session write.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MUST_FEC_PROTECT, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen - 1, false)));
+  stream_->WriteOrBufferData(kData1, false, nullptr);
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  // Queue a bytes_consumed write.
+  stream_->WriteOrBufferData(kData2, false, nullptr);
+
+  // Make sure we get the tail of the first write followed by the bytes_consumed
+  InSequence s;
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MUST_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(1, false)));
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MUST_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(kDataLen - 2, false)));
+  stream_->OnCanWrite();
+
+  // And finally the end of the bytes_consumed.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MUST_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(2, true)));
+  stream_->OnCanWrite();
+}
+
+TEST_F(ReliableQuicStreamTest, WriteOrBufferDataWithFecProtectOptional) {
+  Initialize(kShouldProcessData);
+
+  // Set FEC policy on stream.
+  ReliableQuicStreamPeer::SetFecPolicy(stream_.get(), FEC_PROTECT_OPTIONAL);
+
+  EXPECT_FALSE(HasWriteBlockedStreams());
+  size_t length = 1 + QuicPacketCreator::StreamFramePacketOverhead(
+      PACKET_8BYTE_CONNECTION_ID, !kIncludeVersion,
+      PACKET_6BYTE_SEQUENCE_NUMBER, 0u, NOT_IN_FEC_GROUP);
+  QuicConnectionPeer::GetPacketCreator(connection_)->set_max_packet_length(
+      length);
+
+  // Write first data onto stream, which will cause one session write.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MAY_FEC_PROTECT, _)).WillOnce(
+      Return(QuicConsumedData(kDataLen - 1, false)));
+  stream_->WriteOrBufferData(kData1, false, nullptr);
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  // Queue a bytes_consumed write.
+  stream_->WriteOrBufferData(kData2, false, nullptr);
+
+  // Make sure we get the tail of the first write followed by the bytes_consumed
+  InSequence s;
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MAY_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(1, false)));
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MAY_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(kDataLen - 2, false)));
+  stream_->OnCanWrite();
+
+  // And finally the end of the bytes_consumed.
+  EXPECT_CALL(*session_, WritevData(_, _, _, _, MAY_FEC_PROTECT, _)).
+      WillOnce(Return(QuicConsumedData(2, true)));
+  stream_->OnCanWrite();
+}
+
+TEST_F(ReliableQuicStreamTest, ConnectionCloseAfterStreamClose) {
+  Initialize(kShouldProcessData);
+
+  stream_->CloseReadSide();
+  stream_->CloseWriteSide();
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+  EXPECT_EQ(QUIC_NO_ERROR, stream_->connection_error());
+  stream_->OnConnectionClosed(QUIC_INTERNAL_ERROR, false);
+  EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+  EXPECT_EQ(QUIC_NO_ERROR, stream_->connection_error());
+}
+
+TEST_F(ReliableQuicStreamTest, RstAlwaysSentIfNoFinSent) {
+  // For flow control accounting, a stream must send either a FIN or a RST frame
+  // before termination.
+  // Test that if no FIN has been sent, we send a RST.
+
+  Initialize(kShouldProcessData);
+  EXPECT_FALSE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+
+  // Write some data, with no FIN.
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(1, false)));
+  stream_->WriteOrBufferData(StringPiece(kData1, 1), false, nullptr);
+  EXPECT_FALSE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+
+  // Now close the stream, and expect that we send a RST.
+  EXPECT_CALL(*session_, SendRstStream(_, _, _));
+  stream_->OnClose();
+  EXPECT_FALSE(fin_sent());
+  EXPECT_TRUE(rst_sent());
+}
+
+TEST_F(ReliableQuicStreamTest, RstNotSentIfFinSent) {
+  // For flow control accounting, a stream must send either a FIN or a RST frame
+  // before termination.
+  // Test that if a FIN has been sent, we don't also send a RST.
+
+  Initialize(kShouldProcessData);
+  EXPECT_FALSE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+
+  // Write some data, with FIN.
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(1, true)));
+  stream_->WriteOrBufferData(StringPiece(kData1, 1), true, nullptr);
+  EXPECT_TRUE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+
+  // Now close the stream, and expect that we do not send a RST.
+  stream_->OnClose();
+  EXPECT_TRUE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+}
+
+TEST_F(ReliableQuicStreamTest, OnlySendOneRst) {
+  // For flow control accounting, a stream must send either a FIN or a RST frame
+  // before termination.
+  // Test that if a stream sends a RST, it doesn't send an additional RST during
+  // OnClose() (this shouldn't be harmful, but we shouldn't do it anyway...)
+
+  Initialize(kShouldProcessData);
+  EXPECT_FALSE(fin_sent());
+  EXPECT_FALSE(rst_sent());
+
+  // Reset the stream.
+  const int expected_resets = 1;
+  EXPECT_CALL(*session_, SendRstStream(_, _, _)).Times(expected_resets);
+  stream_->Reset(QUIC_STREAM_CANCELLED);
+  EXPECT_FALSE(fin_sent());
+  EXPECT_TRUE(rst_sent());
+
+  // Now close the stream (any further resets being sent would break the
+  // expectation above).
+  stream_->OnClose();
+  EXPECT_FALSE(fin_sent());
+  EXPECT_TRUE(rst_sent());
+}
+
+TEST_F(ReliableQuicStreamTest, StreamFlowControlMultipleWindowUpdates) {
+  set_initial_flow_control_window_bytes(1000);
+
+  Initialize(kShouldProcessData);
+
+  // If we receive multiple WINDOW_UPDATES (potentially out of order), then we
+  // want to make sure we latch the largest offset we see.
+
+  // Initially should be default.
+  EXPECT_EQ(
+      initial_flow_control_window_bytes_,
+      QuicFlowControllerPeer::SendWindowOffset(stream_->flow_controller()));
+
+  // Check a single WINDOW_UPDATE results in correct offset.
+  QuicWindowUpdateFrame window_update_1(stream_->id(), 1234);
+  stream_->OnWindowUpdateFrame(window_update_1);
+  EXPECT_EQ(
+      window_update_1.byte_offset,
+      QuicFlowControllerPeer::SendWindowOffset(stream_->flow_controller()));
+
+  // Now send a few more WINDOW_UPDATES and make sure that only the largest is
+  // remembered.
+  QuicWindowUpdateFrame window_update_2(stream_->id(), 1);
+  QuicWindowUpdateFrame window_update_3(stream_->id(), 9999);
+  QuicWindowUpdateFrame window_update_4(stream_->id(), 5678);
+  stream_->OnWindowUpdateFrame(window_update_2);
+  stream_->OnWindowUpdateFrame(window_update_3);
+  stream_->OnWindowUpdateFrame(window_update_4);
+  EXPECT_EQ(
+      window_update_3.byte_offset,
+      QuicFlowControllerPeer::SendWindowOffset(stream_->flow_controller()));
+}
+
+void SaveProxyAckNotifierDelegate(
+    scoped_refptr<QuicAckNotifier::DelegateInterface>* delegate_out,
+    QuicAckNotifier::DelegateInterface* delegate) {
+  *delegate_out = delegate;
+}
+
+TEST_F(ReliableQuicStreamTest, WriteOrBufferDataWithQuicAckNotifier) {
+  Initialize(kShouldProcessData);
+
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  const int kDataSize = 16 * 1024;
+  const string kData(kDataSize, 'a');
+
+  const int kFirstWriteSize = 100;
+  const int kSecondWriteSize = 50;
+  const int kLastWriteSize = kDataSize - kFirstWriteSize - kSecondWriteSize;
+
+  // Set a large flow control send window so this doesn't interfere with test.
+  stream_->flow_controller()->UpdateSendWindowOffset(kDataSize + 1);
+  session_->flow_controller()->UpdateSendWindowOffset(kDataSize + 1);
+
+  scoped_refptr<QuicAckNotifier::DelegateInterface> proxy_delegate;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(kFirstWriteSize, false))));
+  stream_->WriteOrBufferData(kData, false, delegate.get());
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  EXPECT_CALL(*session_,
+              WritevData(kHeadersStreamId, _, _, _, _, proxy_delegate.get()))
+      .WillOnce(Return(QuicConsumedData(kSecondWriteSize, false)));
+  stream_->OnCanWrite();
+
+  // No ack expected for an empty write.
+  EXPECT_CALL(*session_,
+              WritevData(kHeadersStreamId, _, _, _, _, proxy_delegate.get()))
+      .WillOnce(Return(QuicConsumedData(0, false)));
+  stream_->OnCanWrite();
+
+  EXPECT_CALL(*session_,
+              WritevData(kHeadersStreamId, _, _, _, _, proxy_delegate.get()))
+      .WillOnce(Return(QuicConsumedData(kLastWriteSize, false)));
+  stream_->OnCanWrite();
+
+  // There were two writes, so OnAckNotification is not propagated
+  // until the third Ack arrives.
+  proxy_delegate->OnAckNotification(1, 2, 3, 4, zero_);
+  proxy_delegate->OnAckNotification(10, 20, 30, 40, zero_);
+
+  // The arguments to delegate->OnAckNotification are the sum of the
+  // arguments to proxy_delegate OnAckNotification calls.
+  EXPECT_CALL(*delegate.get(), OnAckNotification(111, 222, 333, 444, zero_));
+  proxy_delegate->OnAckNotification(100, 200, 300, 400, zero_);
+}
+
+// Verify delegate behavior when packets are acked before the
+// WritevData call that sends out the last byte.
+TEST_F(ReliableQuicStreamTest, WriteOrBufferDataAckNotificationBeforeFlush) {
+  Initialize(kShouldProcessData);
+
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  const int kDataSize = 16 * 1024;
+  const string kData(kDataSize, 'a');
+
+  const int kInitialWriteSize = 100;
+
+  // Set a large flow control send window so this doesn't interfere with test.
+  stream_->flow_controller()->UpdateSendWindowOffset(kDataSize + 1);
+  session_->flow_controller()->UpdateSendWindowOffset(kDataSize + 1);
+
+  scoped_refptr<QuicAckNotifier::DelegateInterface> proxy_delegate;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(kInitialWriteSize, false))));
+  stream_->WriteOrBufferData(kData, false, delegate.get());
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  // Handle the ack of the first write.
+  proxy_delegate->OnAckNotification(1, 2, 3, 4, zero_);
+  proxy_delegate = nullptr;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _)).WillOnce(
+      DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+            Return(QuicConsumedData(kDataSize - kInitialWriteSize, false))));
+  stream_->OnCanWrite();
+
+  // Handle the ack for the second write.
+  EXPECT_CALL(*delegate.get(), OnAckNotification(101, 202, 303, 404, zero_));
+  proxy_delegate->OnAckNotification(100, 200, 300, 400, zero_);
+}
+
+// Verify delegate behavior when WriteOrBufferData does not buffer.
+TEST_F(ReliableQuicStreamTest, WriteAndBufferDataWithAckNotiferNoBuffer) {
+  Initialize(kShouldProcessData);
+
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  scoped_refptr<QuicAckNotifier::DelegateInterface> proxy_delegate;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(kDataLen, true))));
+  stream_->WriteOrBufferData(kData1, true, delegate.get());
+  EXPECT_FALSE(HasWriteBlockedStreams());
+
+  // Handle the ack.
+  EXPECT_CALL(*delegate.get(), OnAckNotification(1, 2, 3, 4, zero_));
+  proxy_delegate->OnAckNotification(1, 2, 3, 4, zero_);
+}
+
+// Verify delegate behavior when WriteOrBufferData buffers all the data.
+TEST_F(ReliableQuicStreamTest, BufferOnWriteAndBufferDataWithAckNotifer) {
+  Initialize(kShouldProcessData);
+
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  scoped_refptr<QuicAckNotifier::DelegateInterface> proxy_delegate;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(Return(QuicConsumedData(0, false)));
+  stream_->WriteOrBufferData(kData1, true, delegate.get());
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(kDataLen, true))));
+  stream_->OnCanWrite();
+
+  // Handle the ack.
+  EXPECT_CALL(*delegate.get(), OnAckNotification(1, 2, 3, 4, zero_));
+  proxy_delegate->OnAckNotification(1, 2, 3, 4, zero_);
+}
+
+// Verify delegate behavior when WriteOrBufferData when the FIN is
+// sent out in a different packet.
+TEST_F(ReliableQuicStreamTest, WriteAndBufferDataWithAckNotiferOnlyFinRemains) {
+  Initialize(kShouldProcessData);
+
+  scoped_refptr<MockAckNotifierDelegate> delegate(
+      new StrictMock<MockAckNotifierDelegate>);
+
+  scoped_refptr<QuicAckNotifier::DelegateInterface> proxy_delegate;
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(kDataLen, false))));
+  stream_->WriteOrBufferData(kData1, true, delegate.get());
+  EXPECT_TRUE(HasWriteBlockedStreams());
+
+  EXPECT_CALL(*session_, WritevData(kHeadersStreamId, _, _, _, _, _))
+      .WillOnce(DoAll(WithArgs<5>(Invoke(CreateFunctor(
+                          &SaveProxyAckNotifierDelegate, &proxy_delegate))),
+                      Return(QuicConsumedData(0, true))));
+  stream_->OnCanWrite();
+
+  // Handle the acks.
+  proxy_delegate->OnAckNotification(1, 2, 3, 4, zero_);
+  EXPECT_CALL(*delegate.get(), OnAckNotification(11, 22, 33, 44, zero_));
+  proxy_delegate->OnAckNotification(10, 20, 30, 40, zero_);
+}
+
+// Verify that when we receive a packet which violates flow control (i.e. sends
+// too much data on the stream) that the stream sequencer never sees this frame,
+// as we check for violation and close the connection early.
+TEST_F(ReliableQuicStreamTest,
+       StreamSequencerNeverSeesPacketsViolatingFlowControl) {
+  Initialize(kShouldProcessData);
+
+  // Receive a stream frame that violates flow control: the byte offset is
+  // higher than the receive window offset.
+  QuicStreamFrame frame(stream_->id(), false,
+                        kInitialSessionFlowControlWindowForTest + 1,
+                        MakeIOVector("."));
+  EXPECT_GT(frame.offset, QuicFlowControllerPeer::ReceiveWindowOffset(
+                              stream_->flow_controller()));
+
+  // Stream should not accept the frame, and the connection should be closed.
+  EXPECT_CALL(*connection_,
+              SendConnectionClose(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA));
+  stream_->OnStreamFrame(frame);
+}
+
+TEST_F(ReliableQuicStreamTest, FinalByteOffsetFromFin) {
+  Initialize(kShouldProcessData);
+
+  EXPECT_FALSE(stream_->HasFinalReceivedByteOffset());
+
+  QuicStreamFrame stream_frame_no_fin(stream_->id(), false, 1234,
+                                      MakeIOVector("."));
+  stream_->OnStreamFrame(stream_frame_no_fin);
+  EXPECT_FALSE(stream_->HasFinalReceivedByteOffset());
+
+  QuicStreamFrame stream_frame_with_fin(stream_->id(), true, 1234,
+                                        MakeIOVector("."));
+  stream_->OnStreamFrame(stream_frame_with_fin);
+  EXPECT_TRUE(stream_->HasFinalReceivedByteOffset());
+}
+
+TEST_F(ReliableQuicStreamTest, FinalByteOffsetFromRst) {
+  Initialize(kShouldProcessData);
+
+  EXPECT_FALSE(stream_->HasFinalReceivedByteOffset());
+  QuicRstStreamFrame rst_frame(stream_->id(), QUIC_STREAM_CANCELLED, 1234);
+  stream_->OnStreamReset(rst_frame);
+  EXPECT_TRUE(stream_->HasFinalReceivedByteOffset());
+}
+
+}  // namespace
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/spdy_utils.cc b/net/quic/spdy_utils.cc
new file mode 100644
index 0000000..d863746
--- /dev/null
+++ b/net/quic/spdy_utils.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/spdy_utils.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+using std::string;
+
+namespace net {
+
+// static
+string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) {
+  int length = SpdyFramer::GetSerializedLength(kDefaultSpdyMajorVersion,
+                                               &headers);
+  SpdyFrameBuilder builder(length, kDefaultSpdyMajorVersion);
+  SpdyFramer::WriteHeaderBlock(&builder, kDefaultSpdyMajorVersion, &headers);
+  scoped_ptr<SpdyFrame> block(builder.take());
+  return string(block->data(), length);
+}
+
+}  // namespace net
diff --git a/net/quic/spdy_utils.h b/net/quic/spdy_utils.h
new file mode 100644
index 0000000..efb72a0
--- /dev/null
+++ b/net/quic/spdy_utils.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_SPDY_UTILS_H_
+#define NET_QUIC_SPDY_UTILS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+const SpdyMajorVersion kDefaultSpdyMajorVersion = SPDY3;
+
+class NET_EXPORT_PRIVATE SpdyUtils {
+ public:
+  static std::string SerializeUncompressedHeaders(
+      const SpdyHeaderBlock& headers);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SpdyUtils);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_SPDY_UTILS_H_
diff --git a/net/quic/test_tools/crypto_test_utils.cc b/net/quic/test_tools/crypto_test_utils.cc
new file mode 100644
index 0000000..cf8df9a
--- /dev/null
+++ b/net/quic/test_tools/crypto_test_utils.cc
@@ -0,0 +1,636 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_crypto_server_stream.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_server_id.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+namespace {
+
+const char kServerHostname[] = "test.example.com";
+const uint16 kServerPort = 80;
+
+// CryptoFramerVisitor is a framer visitor that records handshake messages.
+class CryptoFramerVisitor : public CryptoFramerVisitorInterface {
+ public:
+  CryptoFramerVisitor()
+      : error_(false) {
+  }
+
+  virtual void OnError(CryptoFramer* framer) OVERRIDE { error_ = true; }
+
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE {
+    messages_.push_back(message);
+  }
+
+  bool error() const {
+    return error_;
+  }
+
+  const vector<CryptoHandshakeMessage>& messages() const {
+    return messages_;
+  }
+
+ private:
+  bool error_;
+  vector<CryptoHandshakeMessage> messages_;
+};
+
+// MovePackets parses crypto handshake messages from packet number
+// |*inout_packet_index| through to the last packet (or until a packet fails to
+// decrypt) and has |dest_stream| process them. |*inout_packet_index| is updated
+// with an index one greater than the last packet processed.
+void MovePackets(PacketSavingConnection* source_conn,
+                 size_t *inout_packet_index,
+                 QuicCryptoStream* dest_stream,
+                 PacketSavingConnection* dest_conn) {
+  SimpleQuicFramer framer(source_conn->supported_versions());
+  CryptoFramer crypto_framer;
+  CryptoFramerVisitor crypto_visitor;
+
+  // In order to properly test the code we need to perform encryption and
+  // decryption so that the crypters latch when expected. The crypters are in
+  // |dest_conn|, but we don't want to try and use them there. Instead we swap
+  // them into |framer|, perform the decryption with them, and then swap them
+  // back.
+  QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
+
+  crypto_framer.set_visitor(&crypto_visitor);
+
+  size_t index = *inout_packet_index;
+  for (; index < source_conn->encrypted_packets_.size(); index++) {
+    if (!framer.ProcessPacket(*source_conn->encrypted_packets_[index])) {
+      // The framer will be unable to decrypt forward-secure packets sent after
+      // the handshake is complete. Don't treat them as handshake packets.
+      break;
+    }
+
+    for (vector<QuicStreamFrame>::const_iterator
+         i =  framer.stream_frames().begin();
+         i != framer.stream_frames().end(); ++i) {
+      scoped_ptr<string> frame_data(i->GetDataAsString());
+      ASSERT_TRUE(crypto_framer.ProcessInput(*frame_data));
+      ASSERT_FALSE(crypto_visitor.error());
+    }
+  }
+  *inout_packet_index = index;
+
+  QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
+
+  ASSERT_EQ(0u, crypto_framer.InputBytesRemaining());
+
+  for (vector<CryptoHandshakeMessage>::const_iterator
+       i = crypto_visitor.messages().begin();
+       i != crypto_visitor.messages().end(); ++i) {
+    dest_stream->OnHandshakeMessage(*i);
+  }
+}
+
+// HexChar parses |c| as a hex character. If valid, it sets |*value| to the
+// value of the hex character and returns true. Otherwise it returns false.
+bool HexChar(char c, uint8* value) {
+  if (c >= '0' && c <= '9') {
+    *value = c - '0';
+    return true;
+  }
+  if (c >= 'a' && c <= 'f') {
+    *value = c - 'a' + 10;
+    return true;
+  }
+  if (c >= 'A' && c <= 'F') {
+    *value = c - 'A' + 10;
+    return true;
+  }
+  return false;
+}
+
+// A ChannelIDSource that works in asynchronous mode unless the |callback|
+// argument to GetChannelIDKey is nullptr.
+class AsyncTestChannelIDSource : public ChannelIDSource,
+                                 public CryptoTestUtils::CallbackSource {
+ public:
+  // Takes ownership of |sync_source|, a synchronous ChannelIDSource.
+  explicit AsyncTestChannelIDSource(ChannelIDSource* sync_source)
+      : sync_source_(sync_source) {}
+  virtual ~AsyncTestChannelIDSource() {}
+
+  // ChannelIDSource implementation.
+  virtual QuicAsyncStatus GetChannelIDKey(
+      const string& hostname,
+      scoped_ptr<ChannelIDKey>* channel_id_key,
+      ChannelIDSourceCallback* callback) OVERRIDE {
+    // Synchronous mode.
+    if (!callback) {
+      return sync_source_->GetChannelIDKey(hostname, channel_id_key, nullptr);
+    }
+
+    // Asynchronous mode.
+    QuicAsyncStatus status =
+        sync_source_->GetChannelIDKey(hostname, &channel_id_key_, nullptr);
+    if (status != QUIC_SUCCESS) {
+      return QUIC_FAILURE;
+    }
+    callback_.reset(callback);
+    return QUIC_PENDING;
+  }
+
+  // CallbackSource implementation.
+  virtual void RunPendingCallbacks() OVERRIDE {
+    if (callback_.get()) {
+      callback_->Run(&channel_id_key_);
+      callback_.reset();
+    }
+  }
+
+ private:
+  scoped_ptr<ChannelIDSource> sync_source_;
+  scoped_ptr<ChannelIDSourceCallback> callback_;
+  scoped_ptr<ChannelIDKey> channel_id_key_;
+};
+
+}  // anonymous namespace
+
+CryptoTestUtils::FakeClientOptions::FakeClientOptions()
+    : dont_verify_certs(false),
+      channel_id_enabled(false),
+      channel_id_source_async(false) {
+}
+
+// static
+int CryptoTestUtils::HandshakeWithFakeServer(
+    PacketSavingConnection* client_conn,
+    QuicCryptoClientStream* client) {
+  PacketSavingConnection* server_conn =
+      new PacketSavingConnection(true, client_conn->supported_versions());
+  TestSession server_session(server_conn, DefaultQuicConfig());
+  server_session.InitializeSession();
+  QuicCryptoServerConfig crypto_config(QuicCryptoServerConfig::TESTING,
+                                       QuicRandom::GetInstance());
+
+  SetupCryptoServerConfigForTest(
+      server_session.connection()->clock(),
+      server_session.connection()->random_generator(),
+      server_session.config(), &crypto_config);
+
+  QuicCryptoServerStream server(crypto_config, &server_session);
+  server_session.SetCryptoStream(&server);
+
+  // The client's handshake must have been started already.
+  CHECK_NE(0u, client_conn->packets_.size());
+
+  CommunicateHandshakeMessages(client_conn, client, server_conn, &server);
+
+  CompareClientAndServerKeys(client, &server);
+
+  return client->num_sent_client_hellos();
+}
+
+// static
+int CryptoTestUtils::HandshakeWithFakeClient(
+    PacketSavingConnection* server_conn,
+    QuicCryptoServerStream* server,
+    const FakeClientOptions& options) {
+  PacketSavingConnection* client_conn = new PacketSavingConnection(false);
+  TestClientSession client_session(client_conn, DefaultQuicConfig());
+  QuicCryptoClientConfig crypto_config;
+
+  client_session.config()->SetDefaults();
+  crypto_config.SetDefaults();
+  if (!options.dont_verify_certs) {
+    // TODO(wtc): replace this with ProofVerifierForTesting() when we have
+    // a working ProofSourceForTesting().
+    crypto_config.SetProofVerifier(FakeProofVerifierForTesting());
+  }
+  bool is_https = false;
+  AsyncTestChannelIDSource* async_channel_id_source = nullptr;
+  if (options.channel_id_enabled) {
+    is_https = true;
+
+    ChannelIDSource* source = ChannelIDSourceForTesting();
+    if (options.channel_id_source_async) {
+      async_channel_id_source = new AsyncTestChannelIDSource(source);
+      source = async_channel_id_source;
+    }
+    crypto_config.SetChannelIDSource(source);
+  }
+  QuicServerId server_id(kServerHostname, kServerPort, is_https,
+                         PRIVACY_MODE_DISABLED);
+  QuicCryptoClientStream client(server_id, &client_session,
+                                ProofVerifyContextForTesting(),
+                                &crypto_config);
+  client_session.SetCryptoStream(&client);
+
+  CHECK(client.CryptoConnect());
+  CHECK_EQ(1u, client_conn->packets_.size());
+
+  CommunicateHandshakeMessagesAndRunCallbacks(
+      client_conn, &client, server_conn, server, async_channel_id_source);
+
+  CompareClientAndServerKeys(&client, server);
+
+  if (options.channel_id_enabled) {
+    scoped_ptr<ChannelIDKey> channel_id_key;
+    QuicAsyncStatus status = crypto_config.channel_id_source()->GetChannelIDKey(
+        kServerHostname, &channel_id_key, nullptr);
+    EXPECT_EQ(QUIC_SUCCESS, status);
+    EXPECT_EQ(channel_id_key->SerializeKey(),
+              server->crypto_negotiated_params().channel_id);
+    EXPECT_EQ(options.channel_id_source_async,
+              client.WasChannelIDSourceCallbackRun());
+  }
+
+  return client.num_sent_client_hellos();
+}
+
+// static
+void CryptoTestUtils::SetupCryptoServerConfigForTest(
+    const QuicClock* clock,
+    QuicRandom* rand,
+    QuicConfig* config,
+    QuicCryptoServerConfig* crypto_config) {
+  config->SetDefaults();
+  QuicCryptoServerConfig::ConfigOptions options;
+  options.channel_id_enabled = true;
+  scoped_ptr<CryptoHandshakeMessage> scfg(
+      crypto_config->AddDefaultConfig(rand, clock, options));
+}
+
+// static
+void CryptoTestUtils::CommunicateHandshakeMessages(
+    PacketSavingConnection* a_conn,
+    QuicCryptoStream* a,
+    PacketSavingConnection* b_conn,
+    QuicCryptoStream* b) {
+  CommunicateHandshakeMessagesAndRunCallbacks(a_conn, a, b_conn, b, nullptr);
+}
+
+// static
+void CryptoTestUtils::CommunicateHandshakeMessagesAndRunCallbacks(
+    PacketSavingConnection* a_conn,
+    QuicCryptoStream* a,
+    PacketSavingConnection* b_conn,
+    QuicCryptoStream* b,
+    CallbackSource* callback_source) {
+  size_t a_i = 0, b_i = 0;
+  while (!a->handshake_confirmed()) {
+    ASSERT_GT(a_conn->packets_.size(), a_i);
+    LOG(INFO) << "Processing " << a_conn->packets_.size() - a_i
+              << " packets a->b";
+    MovePackets(a_conn, &a_i, b, b_conn);
+    if (callback_source) {
+      callback_source->RunPendingCallbacks();
+    }
+
+    ASSERT_GT(b_conn->packets_.size(), b_i);
+    LOG(INFO) << "Processing " << b_conn->packets_.size() - b_i
+              << " packets b->a";
+    MovePackets(b_conn, &b_i, a, a_conn);
+    if (callback_source) {
+      callback_source->RunPendingCallbacks();
+    }
+  }
+}
+
+// static
+pair<size_t, size_t> CryptoTestUtils::AdvanceHandshake(
+    PacketSavingConnection* a_conn,
+    QuicCryptoStream* a,
+    size_t a_i,
+    PacketSavingConnection* b_conn,
+    QuicCryptoStream* b,
+    size_t b_i) {
+  LOG(INFO) << "Processing " << a_conn->packets_.size() - a_i
+            << " packets a->b";
+  MovePackets(a_conn, &a_i, b, b_conn);
+
+  LOG(INFO) << "Processing " << b_conn->packets_.size() - b_i
+            << " packets b->a";
+  if (b_conn->packets_.size() - b_i == 2) {
+    LOG(INFO) << "here";
+  }
+  MovePackets(b_conn, &b_i, a, a_conn);
+
+  return make_pair(a_i, b_i);
+}
+
+// static
+string CryptoTestUtils::GetValueForTag(const CryptoHandshakeMessage& message,
+                                       QuicTag tag) {
+  QuicTagValueMap::const_iterator it = message.tag_value_map().find(tag);
+  if (it == message.tag_value_map().end()) {
+    return string();
+  }
+  return it->second;
+}
+
+class MockCommonCertSets : public CommonCertSets {
+ public:
+  MockCommonCertSets(StringPiece cert, uint64 hash, uint32 index)
+      : cert_(cert.as_string()),
+        hash_(hash),
+        index_(index) {
+  }
+
+  virtual StringPiece GetCommonHashes() const OVERRIDE {
+    CHECK(false) << "not implemented";
+    return StringPiece();
+  }
+
+  virtual StringPiece GetCert(uint64 hash, uint32 index) const OVERRIDE {
+    if (hash == hash_ && index == index_) {
+      return cert_;
+    }
+    return StringPiece();
+  }
+
+  virtual bool MatchCert(StringPiece cert,
+                         StringPiece common_set_hashes,
+                         uint64* out_hash,
+                         uint32* out_index) const OVERRIDE {
+    if (cert != cert_) {
+      return false;
+    }
+
+    if (common_set_hashes.size() % sizeof(uint64) != 0) {
+      return false;
+    }
+    bool client_has_set = false;
+    for (size_t i = 0; i < common_set_hashes.size(); i += sizeof(uint64)) {
+      uint64 hash;
+      memcpy(&hash, common_set_hashes.data() + i, sizeof(hash));
+      if (hash == hash_) {
+        client_has_set = true;
+        break;
+      }
+    }
+
+    if (!client_has_set) {
+      return false;
+    }
+
+    *out_hash = hash_;
+    *out_index = index_;
+    return true;
+  }
+
+ private:
+  const string cert_;
+  const uint64 hash_;
+  const uint32 index_;
+};
+
+CommonCertSets* CryptoTestUtils::MockCommonCertSets(StringPiece cert,
+                                                    uint64 hash,
+                                                    uint32 index) {
+  return new class MockCommonCertSets(cert, hash, index);
+}
+
+void CryptoTestUtils::CompareClientAndServerKeys(
+    QuicCryptoClientStream* client,
+    QuicCryptoServerStream* server) {
+  const QuicEncrypter* client_encrypter(
+      client->session()->connection()->encrypter(ENCRYPTION_INITIAL));
+  const QuicDecrypter* client_decrypter(
+      client->session()->connection()->decrypter());
+  const QuicEncrypter* client_forward_secure_encrypter(
+      client->session()->connection()->encrypter(ENCRYPTION_FORWARD_SECURE));
+  const QuicDecrypter* client_forward_secure_decrypter(
+      client->session()->connection()->alternative_decrypter());
+  const QuicEncrypter* server_encrypter(
+      server->session()->connection()->encrypter(ENCRYPTION_INITIAL));
+  const QuicDecrypter* server_decrypter(
+      server->session()->connection()->decrypter());
+  const QuicEncrypter* server_forward_secure_encrypter(
+      server->session()->connection()->encrypter(ENCRYPTION_FORWARD_SECURE));
+  const QuicDecrypter* server_forward_secure_decrypter(
+      server->session()->connection()->alternative_decrypter());
+
+  StringPiece client_encrypter_key = client_encrypter->GetKey();
+  StringPiece client_encrypter_iv = client_encrypter->GetNoncePrefix();
+  StringPiece client_decrypter_key = client_decrypter->GetKey();
+  StringPiece client_decrypter_iv = client_decrypter->GetNoncePrefix();
+  StringPiece client_forward_secure_encrypter_key =
+      client_forward_secure_encrypter->GetKey();
+  StringPiece client_forward_secure_encrypter_iv =
+      client_forward_secure_encrypter->GetNoncePrefix();
+  StringPiece client_forward_secure_decrypter_key =
+      client_forward_secure_decrypter->GetKey();
+  StringPiece client_forward_secure_decrypter_iv =
+      client_forward_secure_decrypter->GetNoncePrefix();
+  StringPiece server_encrypter_key = server_encrypter->GetKey();
+  StringPiece server_encrypter_iv = server_encrypter->GetNoncePrefix();
+  StringPiece server_decrypter_key = server_decrypter->GetKey();
+  StringPiece server_decrypter_iv = server_decrypter->GetNoncePrefix();
+  StringPiece server_forward_secure_encrypter_key =
+      server_forward_secure_encrypter->GetKey();
+  StringPiece server_forward_secure_encrypter_iv =
+      server_forward_secure_encrypter->GetNoncePrefix();
+  StringPiece server_forward_secure_decrypter_key =
+      server_forward_secure_decrypter->GetKey();
+  StringPiece server_forward_secure_decrypter_iv =
+      server_forward_secure_decrypter->GetNoncePrefix();
+
+  StringPiece client_subkey_secret =
+      client->crypto_negotiated_params().subkey_secret;
+  StringPiece server_subkey_secret =
+      server->crypto_negotiated_params().subkey_secret;
+
+
+  const char kSampleLabel[] = "label";
+  const char kSampleContext[] = "context";
+  const size_t kSampleOutputLength = 32;
+  string client_key_extraction;
+  string server_key_extraction;
+  EXPECT_TRUE(client->ExportKeyingMaterial(kSampleLabel,
+                                           kSampleContext,
+                                           kSampleOutputLength,
+                                           &client_key_extraction));
+  EXPECT_TRUE(server->ExportKeyingMaterial(kSampleLabel,
+                                           kSampleContext,
+                                           kSampleOutputLength,
+                                           &server_key_extraction));
+
+  CompareCharArraysWithHexError("client write key",
+                                client_encrypter_key.data(),
+                                client_encrypter_key.length(),
+                                server_decrypter_key.data(),
+                                server_decrypter_key.length());
+  CompareCharArraysWithHexError("client write IV",
+                                client_encrypter_iv.data(),
+                                client_encrypter_iv.length(),
+                                server_decrypter_iv.data(),
+                                server_decrypter_iv.length());
+  CompareCharArraysWithHexError("server write key",
+                                server_encrypter_key.data(),
+                                server_encrypter_key.length(),
+                                client_decrypter_key.data(),
+                                client_decrypter_key.length());
+  CompareCharArraysWithHexError("server write IV",
+                                server_encrypter_iv.data(),
+                                server_encrypter_iv.length(),
+                                client_decrypter_iv.data(),
+                                client_decrypter_iv.length());
+  CompareCharArraysWithHexError("client forward secure write key",
+                                client_forward_secure_encrypter_key.data(),
+                                client_forward_secure_encrypter_key.length(),
+                                server_forward_secure_decrypter_key.data(),
+                                server_forward_secure_decrypter_key.length());
+  CompareCharArraysWithHexError("client forward secure write IV",
+                                client_forward_secure_encrypter_iv.data(),
+                                client_forward_secure_encrypter_iv.length(),
+                                server_forward_secure_decrypter_iv.data(),
+                                server_forward_secure_decrypter_iv.length());
+  CompareCharArraysWithHexError("server forward secure write key",
+                                server_forward_secure_encrypter_key.data(),
+                                server_forward_secure_encrypter_key.length(),
+                                client_forward_secure_decrypter_key.data(),
+                                client_forward_secure_decrypter_key.length());
+  CompareCharArraysWithHexError("server forward secure write IV",
+                                server_forward_secure_encrypter_iv.data(),
+                                server_forward_secure_encrypter_iv.length(),
+                                client_forward_secure_decrypter_iv.data(),
+                                client_forward_secure_decrypter_iv.length());
+  CompareCharArraysWithHexError("subkey secret",
+                                client_subkey_secret.data(),
+                                client_subkey_secret.length(),
+                                server_subkey_secret.data(),
+                                server_subkey_secret.length());
+  CompareCharArraysWithHexError("sample key extraction",
+                                client_key_extraction.data(),
+                                client_key_extraction.length(),
+                                server_key_extraction.data(),
+                                server_key_extraction.length());
+}
+
+// static
+QuicTag CryptoTestUtils::ParseTag(const char* tagstr) {
+  const size_t len = strlen(tagstr);
+  CHECK_NE(0u, len);
+
+  QuicTag tag = 0;
+
+  if (tagstr[0] == '#') {
+    CHECK_EQ(static_cast<size_t>(1 + 2*4), len);
+    tagstr++;
+
+    for (size_t i = 0; i < 8; i++) {
+      tag <<= 4;
+
+      uint8 v = 0;
+      CHECK(HexChar(tagstr[i], &v));
+      tag |= v;
+    }
+
+    return tag;
+  }
+
+  CHECK_LE(len, 4u);
+  for (size_t i = 0; i < 4; i++) {
+    tag >>= 8;
+    if (i < len) {
+      tag |= static_cast<uint32>(tagstr[i]) << 24;
+    }
+  }
+
+  return tag;
+}
+
+// static
+CryptoHandshakeMessage CryptoTestUtils::Message(const char* message_tag, ...) {
+  va_list ap;
+  va_start(ap, message_tag);
+
+  CryptoHandshakeMessage message = BuildMessage(message_tag, ap);
+  va_end(ap);
+  return message;
+}
+
+// static
+CryptoHandshakeMessage CryptoTestUtils::BuildMessage(const char* message_tag,
+                                                     va_list ap) {
+  CryptoHandshakeMessage msg;
+  msg.set_tag(ParseTag(message_tag));
+
+  for (;;) {
+    const char* tagstr = va_arg(ap, const char*);
+    if (tagstr == nullptr) {
+      break;
+    }
+
+    if (tagstr[0] == '$') {
+      // Special value.
+      const char* const special = tagstr + 1;
+      if (strcmp(special, "padding") == 0) {
+        const int min_bytes = va_arg(ap, int);
+        msg.set_minimum_size(min_bytes);
+      } else {
+        CHECK(false) << "Unknown special value: " << special;
+      }
+
+      continue;
+    }
+
+    const QuicTag tag = ParseTag(tagstr);
+    const char* valuestr = va_arg(ap, const char*);
+
+    size_t len = strlen(valuestr);
+    if (len > 0 && valuestr[0] == '#') {
+      valuestr++;
+      len--;
+
+      CHECK_EQ(0u, len % 2);
+      scoped_ptr<uint8[]> buf(new uint8[len/2]);
+
+      for (size_t i = 0; i < len/2; i++) {
+        uint8 v = 0;
+        CHECK(HexChar(valuestr[i*2], &v));
+        buf[i] = v << 4;
+        CHECK(HexChar(valuestr[i*2 + 1], &v));
+        buf[i] |= v;
+      }
+
+      msg.SetStringPiece(
+          tag, StringPiece(reinterpret_cast<char*>(buf.get()), len/2));
+      continue;
+    }
+
+    msg.SetStringPiece(tag, valuestr);
+  }
+
+  // The CryptoHandshakeMessage needs to be serialized and parsed to ensure
+  // that any padding is included.
+  scoped_ptr<QuicData> bytes(CryptoFramer::ConstructHandshakeMessage(msg));
+  scoped_ptr<CryptoHandshakeMessage> parsed(
+      CryptoFramer::ParseMessage(bytes->AsStringPiece()));
+  CHECK(parsed.get());
+
+  return *parsed;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/crypto_test_utils.h b/net/quic/test_tools/crypto_test_utils.h
new file mode 100644
index 0000000..c72b904
--- /dev/null
+++ b/net/quic/test_tools/crypto_test_utils.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
+#define NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
+
+#include <stdarg.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ChannelIDSource;
+class CommonCertSets;
+class ProofSource;
+class ProofVerifier;
+class ProofVerifyContext;
+class QuicClock;
+class QuicConfig;
+class QuicCryptoClientStream;
+class QuicCryptoServerConfig;
+class QuicCryptoServerStream;
+class QuicCryptoStream;
+class QuicRandom;
+
+namespace test {
+
+class PacketSavingConnection;
+
+class CryptoTestUtils {
+ public:
+  // An interface for a source of callbacks. This is used for invoking
+  // callbacks asynchronously.
+  //
+  // Call the RunPendingCallbacks method regularly to run the callbacks from
+  // this source.
+  class CallbackSource {
+   public:
+    virtual ~CallbackSource() {}
+
+    // Runs pending callbacks from this source. If there is no pending
+    // callback, does nothing.
+    virtual void RunPendingCallbacks() = 0;
+  };
+
+  // FakeClientOptions bundles together a number of options for configuring
+  // HandshakeWithFakeClient.
+  struct FakeClientOptions {
+    FakeClientOptions();
+
+    // If dont_verify_certs is true then no ProofVerifier is set on the client.
+    // Thus no certificates will be requested or checked.
+    bool dont_verify_certs;
+
+    // If channel_id_enabled is true then the client will attempt to send a
+    // ChannelID.
+    bool channel_id_enabled;
+
+    // If channel_id_source_async is true then the client will use an async
+    // ChannelIDSource for testing. Ignored if channel_id_enabled is false.
+    bool channel_id_source_async;
+  };
+
+  // returns: the number of client hellos that the client sent.
+  static int HandshakeWithFakeServer(PacketSavingConnection* client_conn,
+                                     QuicCryptoClientStream* client);
+
+  // returns: the number of client hellos that the client sent.
+  static int HandshakeWithFakeClient(PacketSavingConnection* server_conn,
+                                     QuicCryptoServerStream* server,
+                                     const FakeClientOptions& options);
+
+  // SetupCryptoServerConfigForTest configures |config| and |crypto_config|
+  // with sensible defaults for testing.
+  static void SetupCryptoServerConfigForTest(
+      const QuicClock* clock,
+      QuicRandom* rand,
+      QuicConfig* config,
+      QuicCryptoServerConfig* crypto_config);
+
+  // CommunicateHandshakeMessages moves messages from |a| to |b| and back until
+  // |a|'s handshake has completed.
+  static void CommunicateHandshakeMessages(PacketSavingConnection* a_conn,
+                                           QuicCryptoStream* a,
+                                           PacketSavingConnection* b_conn,
+                                           QuicCryptoStream* b);
+
+  // CommunicateHandshakeMessagesAndRunCallbacks moves messages from |a| to |b|
+  // and back until |a|'s handshake has completed. If |callback_source| is not
+  // nullptr, CommunicateHandshakeMessagesAndRunCallbacks also runs callbacks
+  // from
+  // |callback_source| between processing messages.
+  static void CommunicateHandshakeMessagesAndRunCallbacks(
+      PacketSavingConnection* a_conn,
+      QuicCryptoStream* a,
+      PacketSavingConnection* b_conn,
+      QuicCryptoStream* b,
+      CallbackSource* callback_source);
+
+  // AdvanceHandshake attempts to moves messages from |a| to |b| and |b| to |a|.
+  // Returns the number of messages moved.
+  static std::pair<size_t, size_t> AdvanceHandshake(
+      PacketSavingConnection* a_conn,
+      QuicCryptoStream* a,
+      size_t a_i,
+      PacketSavingConnection* b_conn,
+      QuicCryptoStream* b,
+      size_t b_i);
+
+  // Returns the value for the tag |tag| in the tag value map of |message|.
+  static std::string GetValueForTag(const CryptoHandshakeMessage& message,
+                                    QuicTag tag);
+
+  // Returns a |ProofSource| that serves up test certificates.
+  static ProofSource* ProofSourceForTesting();
+
+  // Returns a |ProofVerifier| that uses the QUIC testing root CA.
+  static ProofVerifier* ProofVerifierForTesting();
+
+  // Returns a |ProofVerifyContext| that must be used with the verifier
+  // returned by |ProofVerifierForTesting|.
+  static ProofVerifyContext* ProofVerifyContextForTesting();
+
+  // These functions return a fake |ProofSource|, |ProofVerifier|, or
+  // |ProofVerifyContext| that works with each other. These are suitable for
+  // unit tests that aren't concerned with |ProofSource| and |ProofVerifier|.
+  // TODO(wtc): delete these when Chromium has a working
+  // ProofSourceForTesting().
+  static ProofSource* FakeProofSourceForTesting();
+  static ProofVerifier* FakeProofVerifierForTesting();
+  static ProofVerifyContext* FakeProofVerifyContextForTesting();
+
+  // MockCommonCertSets returns a CommonCertSets that contains a single set with
+  // hash |hash|, consisting of the certificate |cert| at index |index|.
+  static CommonCertSets* MockCommonCertSets(base::StringPiece cert,
+                                            uint64 hash,
+                                            uint32 index);
+
+  // ParseTag returns a QuicTag from parsing |tagstr|. |tagstr| may either be
+  // in the format "EXMP" (i.e. ASCII format), or "#11223344" (an explicit hex
+  // format). It CHECK fails if there's a parse error.
+  static QuicTag ParseTag(const char* tagstr);
+
+  // Message constructs a handshake message from a variable number of
+  // arguments. |message_tag| is passed to |ParseTag| and used as the tag of
+  // the resulting message. The arguments are taken in pairs and nullptr
+  // terminated. The first of each pair is the tag of a tag/value and is given
+  // as an argument to |ParseTag|. The second is the value of the tag/value
+  // pair and is either a hex dump, preceeded by a '#', or a raw value.
+  //
+  //   Message(
+  //       "CHLO",
+  //       "NOCE", "#11223344",
+  //       "SNI", "www.example.com",
+  //       nullptr);
+  static CryptoHandshakeMessage Message(const char* message_tag, ...);
+
+  // BuildMessage is the same as |Message|, but takes the variable arguments
+  // explicitly. TODO(rtenneti): Investigate whether it'd be better for
+  // Message() and BuildMessage() to return a CryptoHandshakeMessage* pointer
+  // instead, to avoid copying the return value.
+  static CryptoHandshakeMessage BuildMessage(const char* message_tag,
+                                             va_list ap);
+
+  // ChannelIDSourceForTesting returns a ChannelIDSource that generates keys
+  // deterministically based on the hostname given in the GetChannelIDKey call.
+  // This ChannelIDSource works in synchronous mode, i.e., its GetChannelIDKey
+  // method never returns QUIC_PENDING.
+  static ChannelIDSource* ChannelIDSourceForTesting();
+
+ private:
+  static void CompareClientAndServerKeys(QuicCryptoClientStream* client,
+                                         QuicCryptoServerStream* server);
+
+  DISALLOW_COPY_AND_ASSIGN(CryptoTestUtils);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
diff --git a/net/quic/test_tools/crypto_test_utils_chromium.cc b/net/quic/test_tools/crypto_test_utils_chromium.cc
new file mode 100644
index 0000000..e475e56
--- /dev/null
+++ b/net/quic/test_tools/crypto_test_utils_chromium.cc
@@ -0,0 +1,145 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/transport_security_state.h"
+#include "net/quic/crypto/proof_source_chromium.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/test/cert_test_util.h"
+
+namespace net {
+
+namespace test {
+
+namespace {
+
+class TestProofVerifierChromium : public ProofVerifierChromium {
+ public:
+  TestProofVerifierChromium(CertVerifier* cert_verifier,
+                            TransportSecurityState* transport_security_state,
+                            const std::string& cert_file)
+      : ProofVerifierChromium(cert_verifier, transport_security_state),
+        cert_verifier_(cert_verifier),
+        transport_security_state_(transport_security_state) {
+    // Load and install the root for the validated chain.
+    scoped_refptr<X509Certificate> root_cert =
+        ImportCertFromFile(GetTestCertsDirectory(), cert_file);
+    scoped_root_.Reset(root_cert.get());
+  }
+  virtual ~TestProofVerifierChromium() {}
+
+ private:
+  ScopedTestRoot scoped_root_;
+  scoped_ptr<CertVerifier> cert_verifier_;
+  scoped_ptr<TransportSecurityState> transport_security_state_;
+};
+
+const char kLeafCert[] = "leaf";
+const char kIntermediateCert[] = "intermediate";
+const char kSignature[] = "signature";
+
+class FakeProofSource : public ProofSource {
+ public:
+  FakeProofSource() : certs_(2) {
+    certs_[0] = kLeafCert;
+    certs_[1] = kIntermediateCert;
+  }
+  virtual ~FakeProofSource() {}
+
+  // ProofSource interface
+  virtual bool GetProof(const std::string& hostname,
+                        const std::string& server_config,
+                        bool ecdsa_ok,
+                        const std::vector<std::string>** out_certs,
+                        std::string* out_signature) OVERRIDE {
+    *out_certs = &certs_;
+    *out_signature = kSignature;
+    return true;
+  }
+
+ private:
+  std::vector<std::string> certs_;
+  DISALLOW_COPY_AND_ASSIGN(FakeProofSource);
+};
+
+class FakeProofVerifier : public ProofVerifier {
+ public:
+  FakeProofVerifier() {}
+  virtual ~FakeProofVerifier() {}
+
+  // ProofVerifier interface
+  virtual QuicAsyncStatus VerifyProof(
+      const std::string& hostname,
+      const std::string& server_config,
+      const std::vector<std::string>& certs,
+      const std::string& signature,
+      const ProofVerifyContext* verify_context,
+      std::string* error_details,
+      scoped_ptr<ProofVerifyDetails>* verify_details,
+      ProofVerifierCallback* callback) OVERRIDE {
+    error_details->clear();
+    scoped_ptr<ProofVerifyDetailsChromium> verify_details_chromium(
+        new ProofVerifyDetailsChromium);
+    if (certs.size() != 2 || certs[0] != kLeafCert ||
+        certs[1] != kIntermediateCert || signature != kSignature) {
+      *error_details = "Invalid proof";
+      verify_details_chromium->cert_verify_result.cert_status =
+          CERT_STATUS_INVALID;
+      *verify_details = verify_details_chromium.Pass();
+      return QUIC_FAILURE;
+    }
+    *verify_details = verify_details_chromium.Pass();
+    return QUIC_SUCCESS;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeProofVerifier);
+};
+
+}  // namespace
+
+// static
+ProofSource* CryptoTestUtils::ProofSourceForTesting() {
+  return new ProofSourceChromium();
+}
+
+// static
+ProofVerifier* CryptoTestUtils::ProofVerifierForTesting() {
+  TestProofVerifierChromium* proof_verifier =
+      new TestProofVerifierChromium(CertVerifier::CreateDefault(),
+                                    new TransportSecurityState,
+                                    "quic_root.crt");
+  return proof_verifier;
+}
+
+// static
+ProofVerifyContext* CryptoTestUtils::ProofVerifyContextForTesting() {
+  return new ProofVerifyContextChromium(BoundNetLog());
+}
+
+// static
+ProofSource* CryptoTestUtils::FakeProofSourceForTesting() {
+  return new FakeProofSource();
+}
+
+// static
+ProofVerifier* CryptoTestUtils::FakeProofVerifierForTesting() {
+  return new FakeProofVerifier();
+}
+
+// static
+ProofVerifyContext* CryptoTestUtils::FakeProofVerifyContextForTesting() {
+  return nullptr;
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/net/quic/test_tools/crypto_test_utils_nss.cc b/net/quic/test_tools/crypto_test_utils_nss.cc
new file mode 100644
index 0000000..eaf0ecd
--- /dev/null
+++ b/net/quic/test_tools/crypto_test_utils_nss.cc
@@ -0,0 +1,64 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/channel_id_chromium.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+namespace test {
+
+class TestChannelIDSource : public ChannelIDSource {
+ public:
+  virtual ~TestChannelIDSource() {
+    STLDeleteValues(&hostname_to_key_);
+  }
+
+  // ChannelIDSource implementation.
+
+  virtual QuicAsyncStatus GetChannelIDKey(
+      const string& hostname,
+      scoped_ptr<ChannelIDKey>* channel_id_key,
+      ChannelIDSourceCallback* /*callback*/) OVERRIDE {
+    channel_id_key->reset(new ChannelIDKeyChromium(HostnameToKey(hostname)));
+    return QUIC_SUCCESS;
+  }
+
+ private:
+  typedef std::map<string, crypto::ECPrivateKey*> HostnameToKeyMap;
+
+  crypto::ECPrivateKey* HostnameToKey(const string& hostname) {
+    HostnameToKeyMap::const_iterator it = hostname_to_key_.find(hostname);
+    if (it != hostname_to_key_.end()) {
+      return it->second->Copy();
+    }
+
+    crypto::ECPrivateKey* keypair = crypto::ECPrivateKey::Create();
+    if (!keypair) {
+      return nullptr;
+    }
+    hostname_to_key_[hostname] = keypair;
+    return keypair->Copy();
+  }
+
+  HostnameToKeyMap hostname_to_key_;
+};
+
+// static
+ChannelIDSource* CryptoTestUtils::ChannelIDSourceForTesting() {
+  return new TestChannelIDSource();
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/net/quic/test_tools/crypto_test_utils_openssl.cc b/net/quic/test_tools/crypto_test_utils_openssl.cc
new file mode 100644
index 0000000..15babd4
--- /dev/null
+++ b/net/quic/test_tools/crypto_test_utils_openssl.cc
@@ -0,0 +1,167 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/sha.h>
+
+#include "crypto/openssl_util.h"
+#include "crypto/scoped_openssl_types.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/channel_id.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+namespace test {
+
+class TestChannelIDKey : public ChannelIDKey {
+ public:
+  explicit TestChannelIDKey(EVP_PKEY* ecdsa_key) : ecdsa_key_(ecdsa_key) {}
+  virtual ~TestChannelIDKey() OVERRIDE {}
+
+  // ChannelIDKey implementation.
+
+  virtual bool Sign(StringPiece signed_data,
+                    string* out_signature) const OVERRIDE {
+    crypto::ScopedEVP_MD_CTX md_ctx(EVP_MD_CTX_create());
+    if (!md_ctx ||
+        EVP_DigestSignInit(md_ctx.get(), nullptr, EVP_sha256(), nullptr,
+                           ecdsa_key_.get()) != 1) {
+      return false;
+    }
+
+    EVP_DigestUpdate(md_ctx.get(), ChannelIDVerifier::kContextStr,
+                     strlen(ChannelIDVerifier::kContextStr) + 1);
+    EVP_DigestUpdate(md_ctx.get(), ChannelIDVerifier::kClientToServerStr,
+                     strlen(ChannelIDVerifier::kClientToServerStr) + 1);
+    EVP_DigestUpdate(md_ctx.get(), signed_data.data(), signed_data.size());
+
+    size_t sig_len;
+    if (!EVP_DigestSignFinal(md_ctx.get(), nullptr, &sig_len)) {
+      return false;
+    }
+
+    scoped_ptr<uint8[]> der_sig(new uint8[sig_len]);
+    if (!EVP_DigestSignFinal(md_ctx.get(), der_sig.get(), &sig_len)) {
+      return false;
+    }
+
+    uint8* derp = der_sig.get();
+    crypto::ScopedECDSA_SIG sig(
+        d2i_ECDSA_SIG(nullptr, const_cast<const uint8**>(&derp), sig_len));
+    if (sig.get() == nullptr) {
+      return false;
+    }
+
+    // The signature consists of a pair of 32-byte numbers.
+    static const size_t kSignatureLength = 32 * 2;
+    scoped_ptr<uint8[]> signature(new uint8[kSignatureLength]);
+    memset(signature.get(), 0, kSignatureLength);
+    BN_bn2bin(sig.get()->r, signature.get() + 32 - BN_num_bytes(sig.get()->r));
+    BN_bn2bin(sig.get()->s, signature.get() + 64 - BN_num_bytes(sig.get()->s));
+
+    *out_signature = string(reinterpret_cast<char*>(signature.get()),
+                            kSignatureLength);
+
+    return true;
+  }
+
+  virtual string SerializeKey() const OVERRIDE {
+    // i2d_PublicKey will produce an ANSI X9.62 public key which, for a P-256
+    // key, is 0x04 (meaning uncompressed) followed by the x and y field
+    // elements as 32-byte, big-endian numbers.
+    static const int kExpectedKeyLength = 65;
+
+    int len = i2d_PublicKey(ecdsa_key_.get(), nullptr);
+    if (len != kExpectedKeyLength) {
+      return "";
+    }
+
+    uint8 buf[kExpectedKeyLength];
+    uint8* derp = buf;
+    i2d_PublicKey(ecdsa_key_.get(), &derp);
+
+    return string(reinterpret_cast<char*>(buf + 1), kExpectedKeyLength - 1);
+  }
+
+ private:
+  crypto::ScopedEVP_PKEY ecdsa_key_;
+};
+
+class TestChannelIDSource : public ChannelIDSource {
+ public:
+  virtual ~TestChannelIDSource() {}
+
+  // ChannelIDSource implementation.
+
+  virtual QuicAsyncStatus GetChannelIDKey(
+      const string& hostname,
+      scoped_ptr<ChannelIDKey>* channel_id_key,
+      ChannelIDSourceCallback* /*callback*/) OVERRIDE {
+    channel_id_key->reset(new TestChannelIDKey(HostnameToKey(hostname)));
+    return QUIC_SUCCESS;
+  }
+
+ private:
+  static EVP_PKEY* HostnameToKey(const string& hostname) {
+    // In order to generate a deterministic key for a given hostname the
+    // hostname is hashed with SHA-256 and the resulting digest is treated as a
+    // big-endian number. The most-significant bit is cleared to ensure that
+    // the resulting value is less than the order of the group and then it's
+    // taken as a private key. Given the private key, the public key is
+    // calculated with a group multiplication.
+    SHA256_CTX sha256;
+    SHA256_Init(&sha256);
+    SHA256_Update(&sha256, hostname.data(), hostname.size());
+
+    unsigned char digest[SHA256_DIGEST_LENGTH];
+    SHA256_Final(digest, &sha256);
+
+    // Ensure that the digest is less than the order of the P-256 group by
+    // clearing the most-significant bit.
+    digest[0] &= 0x7f;
+
+    crypto::ScopedBIGNUM k(BN_new());
+    CHECK(BN_bin2bn(digest, sizeof(digest), k.get()) != nullptr);
+
+    crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free>::Type p256(
+        EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+    CHECK(p256.get());
+
+    crypto::ScopedEC_KEY ecdsa_key(EC_KEY_new());
+    CHECK(ecdsa_key.get() != nullptr &&
+          EC_KEY_set_group(ecdsa_key.get(), p256.get()));
+
+    crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free>::Type point(
+        EC_POINT_new(p256.get()));
+    CHECK(EC_POINT_mul(p256.get(), point.get(), k.get(), nullptr, nullptr,
+                       nullptr));
+
+    EC_KEY_set_private_key(ecdsa_key.get(), k.get());
+    EC_KEY_set_public_key(ecdsa_key.get(), point.get());
+
+    crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
+    // EVP_PKEY_set1_EC_KEY takes a reference so no |release| here.
+    EVP_PKEY_set1_EC_KEY(pkey.get(), ecdsa_key.get());
+
+    return pkey.release();
+  }
+};
+
+// static
+ChannelIDSource* CryptoTestUtils::ChannelIDSourceForTesting() {
+  return new TestChannelIDSource();
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/net/quic/test_tools/delayed_verify_strike_register_client.cc b/net/quic/test_tools/delayed_verify_strike_register_client.cc
new file mode 100644
index 0000000..b14a118
--- /dev/null
+++ b/net/quic/test_tools/delayed_verify_strike_register_client.cc
@@ -0,0 +1,53 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/delayed_verify_strike_register_client.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+DelayedVerifyStrikeRegisterClient::DelayedVerifyStrikeRegisterClient(
+    unsigned max_entries,
+    uint32 current_time_external,
+    uint32 window_secs,
+    const uint8 orbit[8],
+    StrikeRegister::StartupType startup)
+    : LocalStrikeRegisterClient(max_entries, current_time_external,
+                                window_secs, orbit, startup),
+      delay_verifications_(false) {
+}
+
+DelayedVerifyStrikeRegisterClient::~DelayedVerifyStrikeRegisterClient() {}
+
+void DelayedVerifyStrikeRegisterClient::VerifyNonceIsValidAndUnique(
+    StringPiece nonce,
+    QuicWallTime now,
+    ResultCallback* cb) {
+  if (delay_verifications_) {
+    pending_verifications_.push_back(VerifyArgs(nonce, now, cb));
+  } else {
+    LocalStrikeRegisterClient::VerifyNonceIsValidAndUnique(nonce, now, cb);
+  }
+}
+
+int DelayedVerifyStrikeRegisterClient::PendingVerifications() const {
+  return pending_verifications_.size();
+}
+
+void DelayedVerifyStrikeRegisterClient::RunPendingVerifications() {
+  vector<VerifyArgs> pending;
+  pending_verifications_.swap(pending);
+  for (vector<VerifyArgs>::const_iterator it = pending.begin(),
+           end = pending.end(); it != end; ++it) {
+    LocalStrikeRegisterClient::VerifyNonceIsValidAndUnique(
+        it->nonce, it->now, it->cb);
+  }
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/delayed_verify_strike_register_client.h b/net/quic/test_tools/delayed_verify_strike_register_client.h
new file mode 100644
index 0000000..6ae7ba1
--- /dev/null
+++ b/net/quic/test_tools/delayed_verify_strike_register_client.h
@@ -0,0 +1,65 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_DELAYED_VERIFY_STRIKE_REGISTER_CLIENT_H_
+#define NET_QUIC_TEST_TOOLS_DELAYED_VERIFY_STRIKE_REGISTER_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/local_strike_register_client.h"
+
+namespace net {
+namespace test {
+
+// Test helper that allows delaying execution of nonce verification
+// callbacks until a later time.
+class DelayedVerifyStrikeRegisterClient : public LocalStrikeRegisterClient {
+ public:
+  DelayedVerifyStrikeRegisterClient(unsigned max_entries,
+                                    uint32 current_time_external,
+                                    uint32 window_secs,
+                                    const uint8 orbit[8],
+                                    StrikeRegister::StartupType startup);
+  virtual ~DelayedVerifyStrikeRegisterClient();
+
+  virtual void VerifyNonceIsValidAndUnique(base::StringPiece nonce,
+                                           QuicWallTime now,
+                                           ResultCallback* cb) OVERRIDE;
+
+  // Start queueing verifications instead of executing them immediately.
+  void StartDelayingVerification() {
+    delay_verifications_ = true;
+  }
+  // Number of verifications that are queued.
+  int PendingVerifications() const;
+  // Run all pending verifications.
+  void RunPendingVerifications();
+
+ private:
+  struct VerifyArgs {
+    VerifyArgs(base::StringPiece in_nonce,
+               QuicWallTime in_now,
+               ResultCallback* in_cb)
+        : nonce(in_nonce.as_string()),
+          now(in_now),
+          cb(in_cb) {
+    }
+
+    std::string nonce;
+    QuicWallTime now;
+    ResultCallback* cb;
+  };
+
+  bool delay_verifications_;
+  std::vector<VerifyArgs> pending_verifications_;
+
+  DISALLOW_COPY_AND_ASSIGN(DelayedVerifyStrikeRegisterClient);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_DELAYED_VERIFY_STRIKE_REGISTER_CLIENT_H_
diff --git a/net/quic/test_tools/mock_clock.cc b/net/quic/test_tools/mock_clock.cc
new file mode 100644
index 0000000..47f2380
--- /dev/null
+++ b/net/quic/test_tools/mock_clock.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/mock_clock.h"
+
+namespace net {
+
+MockClock::MockClock() : now_(QuicTime::Zero()) {
+}
+
+MockClock::~MockClock() {
+}
+
+void MockClock::AdvanceTime(QuicTime::Delta delta) {
+  now_ = now_.Add(delta);
+}
+
+QuicTime MockClock::Now() const {
+  return now_;
+}
+
+QuicTime MockClock::ApproximateNow() const {
+  return now_;
+}
+
+QuicWallTime MockClock::WallNow() const {
+  return QuicWallTime::FromUNIXSeconds(
+      now_.Subtract(QuicTime::Zero()).ToSeconds());
+}
+
+base::TimeTicks MockClock::NowInTicks() const {
+  base::TimeTicks ticks;
+  return ticks + base::TimeDelta::FromMicroseconds(
+      now_.Subtract(QuicTime::Zero()).ToMicroseconds());
+}
+
+}  // namespace net
diff --git a/net/quic/test_tools/mock_clock.h b/net/quic/test_tools/mock_clock.h
new file mode 100644
index 0000000..d6e490f
--- /dev/null
+++ b/net/quic/test_tools/mock_clock.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
+
+#include "net/quic/quic_clock.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace net {
+
+class MockClock : public QuicClock {
+ public:
+  MockClock();
+  virtual ~MockClock();
+
+  void AdvanceTime(QuicTime::Delta delta);
+
+  virtual QuicTime Now() const OVERRIDE;
+
+  virtual QuicTime ApproximateNow() const OVERRIDE;
+
+  virtual QuicWallTime WallNow() const OVERRIDE;
+
+  base::TimeTicks NowInTicks() const;
+
+ private:
+  QuicTime now_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockClock);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
diff --git a/net/quic/test_tools/mock_crypto_client_stream.cc b/net/quic/test_tools/mock_crypto_client_stream.cc
new file mode 100644
index 0000000..dd0e30b
--- /dev/null
+++ b/net/quic/test_tools/mock_crypto_client_stream.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/mock_crypto_client_stream.h"
+
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/quic_client_session_base.h"
+#include "net/quic/quic_server_id.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+MockCryptoClientStream::MockCryptoClientStream(
+    const QuicServerId& server_id,
+    QuicClientSessionBase* session,
+    ProofVerifyContext* verify_context,
+    QuicCryptoClientConfig* crypto_config,
+    HandshakeMode handshake_mode,
+    const ProofVerifyDetails* proof_verify_details)
+    : QuicCryptoClientStream(server_id, session, verify_context,
+                             crypto_config),
+      handshake_mode_(handshake_mode),
+      proof_verify_details_(proof_verify_details) {
+}
+
+MockCryptoClientStream::~MockCryptoClientStream() {
+}
+
+void MockCryptoClientStream::OnHandshakeMessage(
+    const CryptoHandshakeMessage& message) {
+  CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+}
+
+bool MockCryptoClientStream::CryptoConnect() {
+  switch (handshake_mode_) {
+    case ZERO_RTT: {
+      encryption_established_ = true;
+      handshake_confirmed_ = false;
+      session()->connection()->SetDecrypter(QuicDecrypter::Create(kNULL),
+                                            ENCRYPTION_INITIAL);
+      session()->OnCryptoHandshakeEvent(
+          QuicSession::ENCRYPTION_FIRST_ESTABLISHED);
+      break;
+    }
+
+    case CONFIRM_HANDSHAKE: {
+      encryption_established_ = true;
+      handshake_confirmed_ = true;
+      crypto_negotiated_params_.key_exchange = kC255;
+      crypto_negotiated_params_.aead = kAESG;
+      if (proof_verify_details_) {
+        client_session()->OnProofVerifyDetailsAvailable(*proof_verify_details_);
+      }
+      SetConfigNegotiated();
+      session()->connection()->SetDecrypter(QuicDecrypter::Create(kNULL),
+                                            ENCRYPTION_FORWARD_SECURE);
+      session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+      break;
+    }
+
+    case COLD_START: {
+      handshake_confirmed_ = false;
+      encryption_established_ = false;
+      break;
+    }
+  }
+  return true;
+}
+
+void MockCryptoClientStream::SendOnCryptoHandshakeEvent(
+    QuicSession::CryptoHandshakeEvent event) {
+  encryption_established_ = true;
+  if (event == QuicSession::HANDSHAKE_CONFIRMED) {
+    handshake_confirmed_ = true;
+    SetConfigNegotiated();
+  }
+  session()->OnCryptoHandshakeEvent(event);
+}
+
+void MockCryptoClientStream::SetConfigNegotiated() {
+  ASSERT_FALSE(session()->config()->negotiated());
+  QuicTagVector cgst;
+  // TODO(rtenneti): Enable the following code after BBR code is checked in.
+#if 0
+  cgst.push_back(kTBBR);
+#endif
+  cgst.push_back(kQBIC);
+  session()->config()->SetCongestionFeedback(cgst, kQBIC);
+  session()->config()->SetIdleConnectionStateLifetime(
+      QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs),
+      QuicTime::Delta::FromSeconds(kMaximumIdleTimeoutSecs));
+  session()->config()->SetMaxStreamsPerConnection(
+      2 * kDefaultMaxStreamsPerConnection, kDefaultMaxStreamsPerConnection);
+
+  CryptoHandshakeMessage msg;
+  session()->config()->ToHandshakeMessage(&msg);
+  string error_details;
+  const QuicErrorCode error =
+      session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
+  ASSERT_EQ(QUIC_NO_ERROR, error);
+  ASSERT_TRUE(session()->config()->negotiated());
+}
+
+QuicClientSessionBase* MockCryptoClientStream::client_session() {
+  return reinterpret_cast<QuicClientSessionBase*>(session());
+}
+
+}  // namespace net
diff --git a/net/quic/test_tools/mock_crypto_client_stream.h b/net/quic/test_tools/mock_crypto_client_stream.h
new file mode 100644
index 0000000..e940a1e
--- /dev/null
+++ b/net/quic/test_tools/mock_crypto_client_stream.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
+
+#include <string>
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+class QuicServerId;
+
+class MockCryptoClientStream : public QuicCryptoClientStream {
+ public:
+  // HandshakeMode enumerates the handshake mode MockCryptoClientStream should
+  // mock in CryptoConnect.
+  enum HandshakeMode {
+    // CONFIRM_HANDSHAKE indicates that CryptoConnect will immediately confirm
+    // the handshake and establish encryption.  This behavior will never happen
+    // in the field, but is convenient for higher level tests.
+    CONFIRM_HANDSHAKE,
+
+    // ZERO_RTT indicates that CryptoConnect will establish encryption but will
+    // not confirm the handshake.
+    ZERO_RTT,
+
+    // COLD_START indicates that CryptoConnect will neither establish encryption
+    // nor confirm the handshake
+    COLD_START,
+  };
+
+  MockCryptoClientStream(
+      const QuicServerId& server_id,
+      QuicClientSessionBase* session,
+      ProofVerifyContext* verify_context,
+      QuicCryptoClientConfig* crypto_config,
+      HandshakeMode handshake_mode,
+      const ProofVerifyDetails* proof_verify_details_);
+  virtual ~MockCryptoClientStream();
+
+  // CryptoFramerVisitorInterface implementation.
+  virtual void OnHandshakeMessage(
+      const CryptoHandshakeMessage& message) OVERRIDE;
+
+  // QuicCryptoClientStream implementation.
+  virtual bool CryptoConnect() OVERRIDE;
+
+  // Invokes the sessions's CryptoHandshakeEvent method with the specified
+  // event.
+  void SendOnCryptoHandshakeEvent(QuicSession::CryptoHandshakeEvent event);
+
+  HandshakeMode handshake_mode_;
+
+ private:
+  void SetConfigNegotiated();
+  QuicClientSessionBase* client_session();
+
+  const ProofVerifyDetails* proof_verify_details_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockCryptoClientStream);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
diff --git a/net/quic/test_tools/mock_crypto_client_stream_factory.cc b/net/quic/test_tools/mock_crypto_client_stream_factory.cc
new file mode 100644
index 0000000..5088c43
--- /dev/null
+++ b/net/quic/test_tools/mock_crypto_client_stream_factory.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+
+#include "base/lazy_instance.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_server_id.h"
+
+using std::string;
+
+namespace net {
+
+MockCryptoClientStreamFactory::MockCryptoClientStreamFactory()
+    : handshake_mode_(MockCryptoClientStream::CONFIRM_HANDSHAKE),
+      last_stream_(nullptr),
+      proof_verify_details_(nullptr) {
+}
+
+QuicCryptoClientStream*
+MockCryptoClientStreamFactory::CreateQuicCryptoClientStream(
+    const QuicServerId& server_id,
+    QuicClientSession* session,
+    QuicCryptoClientConfig* crypto_config) {
+  last_stream_ = new MockCryptoClientStream(
+      server_id, session, nullptr, crypto_config, handshake_mode_,
+      proof_verify_details_);
+  return last_stream_;
+}
+
+}  // namespace net
diff --git a/net/quic/test_tools/mock_crypto_client_stream_factory.h b/net/quic/test_tools/mock_crypto_client_stream_factory.h
new file mode 100644
index 0000000..721ec25
--- /dev/null
+++ b/net/quic/test_tools/mock_crypto_client_stream_factory.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
+
+#include <string>
+
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_crypto_client_stream.h"
+
+namespace net {
+
+class QuicServerId;
+
+class MockCryptoClientStreamFactory : public QuicCryptoClientStreamFactory  {
+ public:
+  MockCryptoClientStreamFactory();
+  virtual ~MockCryptoClientStreamFactory() {}
+
+  virtual QuicCryptoClientStream* CreateQuicCryptoClientStream(
+      const QuicServerId& server_id,
+      QuicClientSession* session,
+      QuicCryptoClientConfig* crypto_config) OVERRIDE;
+
+  void set_handshake_mode(
+      MockCryptoClientStream::HandshakeMode handshake_mode) {
+    handshake_mode_ = handshake_mode;
+  }
+
+  void set_proof_verify_details(
+      const ProofVerifyDetails* proof_verify_details) {
+    proof_verify_details_ = proof_verify_details;
+  }
+
+  MockCryptoClientStream* last_stream() const {
+    return last_stream_;
+  }
+
+ private:
+  MockCryptoClientStream::HandshakeMode handshake_mode_;
+  MockCryptoClientStream* last_stream_;
+  const ProofVerifyDetails* proof_verify_details_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockCryptoClientStreamFactory);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
diff --git a/net/quic/test_tools/mock_quic_dispatcher.cc b/net/quic/test_tools/mock_quic_dispatcher.cc
new file mode 100644
index 0000000..9c9da43
--- /dev/null
+++ b/net/quic/test_tools/mock_quic_dispatcher.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/mock_quic_dispatcher.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+namespace net {
+namespace test {
+
+MockQuicDispatcher::MockQuicDispatcher(
+    const QuicConfig& config,
+    const QuicCryptoServerConfig& crypto_config,
+    QuicDispatcher::PacketWriterFactory* packet_writer_factory,
+    QuicConnectionHelperInterface* helper)
+    : QuicDispatcher(config,
+                     crypto_config,
+                     QuicSupportedVersions(),
+                     packet_writer_factory,
+                     helper) {
+}
+
+MockQuicDispatcher::~MockQuicDispatcher() {
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/mock_quic_dispatcher.h b/net/quic/test_tools/mock_quic_dispatcher.h
new file mode 100644
index 0000000..f923790
--- /dev/null
+++ b/net/quic/test_tools/mock_quic_dispatcher.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/quic_crypto_server_config.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_dispatcher.h"
+#include "net/quic/quic_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+namespace test {
+
+class MockQuicDispatcher : public QuicDispatcher {
+ public:
+  MockQuicDispatcher(const QuicConfig& config,
+                     const QuicCryptoServerConfig& crypto_config,
+                     PacketWriterFactory* packet_writer_factory,
+                     QuicConnectionHelperInterface* helper);
+
+  virtual ~MockQuicDispatcher();
+
+  MOCK_METHOD3(ProcessPacket, void(const IPEndPoint& server_address,
+                                   const IPEndPoint& client_address,
+                                   const QuicEncryptedPacket& packet));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockQuicDispatcher);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
diff --git a/net/quic/test_tools/mock_random.cc b/net/quic/test_tools/mock_random.cc
new file mode 100644
index 0000000..ab71d16
--- /dev/null
+++ b/net/quic/test_tools/mock_random.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/mock_random.h"
+
+namespace net {
+
+MockRandom::MockRandom()
+    : base_(0xDEADBEEF),
+      increment_(0) {
+}
+
+MockRandom::MockRandom(uint32 base)
+    : base_(base),
+      increment_(0) {
+}
+
+void MockRandom::RandBytes(void* data, size_t len) {
+  memset(data, 'r' + increment_, len);
+}
+
+uint64 MockRandom::RandUint64() {
+  return base_ + increment_;
+}
+
+void MockRandom::Reseed(const void* additional_entropy, size_t entropy_len) {
+}
+
+void MockRandom::ChangeValue() {
+  increment_++;
+}
+
+}  // namespace net
diff --git a/net/quic/test_tools/mock_random.h b/net/quic/test_tools/mock_random.h
new file mode 100644
index 0000000..53b24b7
--- /dev/null
+++ b/net/quic/test_tools/mock_random.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_random.h"
+
+namespace net {
+
+class MockRandom : public QuicRandom {
+ public:
+  // Initializes base_ to 0xDEADBEEF.
+  MockRandom();
+  explicit MockRandom(uint32 base);
+
+  // QuicRandom:
+  // Fills the |data| buffer with a repeating byte, initially 'r'.
+  virtual void RandBytes(void* data, size_t len) OVERRIDE;
+  // Returns base + the current increment.
+  virtual uint64 RandUint64() OVERRIDE;
+  // Does nothing.
+  virtual void Reseed(const void* additional_entropy,
+                      size_t entropy_len) OVERRIDE;
+
+  // ChangeValue increments |increment_|. This causes the value returned by
+  // |RandUint64| and the byte that |RandBytes| fills with, to change.
+  void ChangeValue();
+
+ private:
+  uint32 base_;
+  uint8 increment_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockRandom);
+};
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
diff --git a/net/quic/test_tools/quic_client_session_peer.cc b/net/quic/test_tools/quic_client_session_peer.cc
new file mode 100644
index 0000000..7554551
--- /dev/null
+++ b/net/quic/test_tools/quic_client_session_peer.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_client_session_peer.h"
+
+#include "net/quic/quic_client_session.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicClientSessionPeer::SetMaxOpenStreams(QuicClientSession* session,
+                                              size_t max_streams,
+                                              size_t default_streams) {
+  session->config()->SetMaxStreamsPerConnection(max_streams, default_streams);
+}
+
+// static
+void QuicClientSessionPeer::SetChannelIDSent(QuicClientSession* session,
+                                             bool channel_id_sent) {
+  session->crypto_stream_->channel_id_sent_ = channel_id_sent;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_client_session_peer.h b/net/quic/test_tools/quic_client_session_peer.h
new file mode 100644
index 0000000..1a721c4
--- /dev/null
+++ b/net/quic/test_tools/quic_client_session_peer.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicClientSession;
+
+namespace test {
+
+class QuicClientSessionPeer {
+ public:
+  static void SetMaxOpenStreams(QuicClientSession* session,
+                                size_t max_streams,
+                                size_t default_streams);
+
+  static void SetChannelIDSent(QuicClientSession* session,
+                               bool channel_id_sent);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicClientSessionPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
diff --git a/net/quic/test_tools/quic_config_peer.cc b/net/quic/test_tools/quic_config_peer.cc
new file mode 100644
index 0000000..f700171
--- /dev/null
+++ b/net/quic/test_tools/quic_config_peer.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_config_peer.h"
+
+#include "net/quic/quic_config.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicConfigPeer::SetReceivedInitialWindow(QuicConfig* config,
+                                              size_t initial_window) {
+  config->initial_congestion_window_.SetReceivedValue(initial_window);
+}
+
+// static
+void QuicConfigPeer::SetReceivedInitialFlowControlWindow(QuicConfig* config,
+                                                         uint32 window_bytes) {
+  config->initial_flow_control_window_bytes_.SetReceivedValue(window_bytes);
+}
+
+// static
+void QuicConfigPeer::SetReceivedInitialStreamFlowControlWindow(
+    QuicConfig* config,
+    uint32 window_bytes) {
+  config->initial_stream_flow_control_window_bytes_.SetReceivedValue(
+      window_bytes);
+}
+
+// static
+void QuicConfigPeer::SetReceivedInitialSessionFlowControlWindow(
+    QuicConfig* config,
+    uint32 window_bytes) {
+  config->initial_session_flow_control_window_bytes_.SetReceivedValue(
+      window_bytes);
+}
+
+// static
+void QuicConfigPeer::SetReceivedConnectionOptions(
+    QuicConfig* config,
+    const QuicTagVector& options) {
+  config->connection_options_.SetReceivedValues(options);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_config_peer.h b/net/quic/test_tools/quic_config_peer.h
new file mode 100644
index 0000000..8e9a5ea
--- /dev/null
+++ b/net/quic/test_tools/quic_config_peer.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CONFIG_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CONFIG_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicConfig;
+
+namespace test {
+
+class QuicConfigPeer {
+ public:
+  static void SetReceivedInitialWindow(QuicConfig* config,
+                                       size_t initial_window);
+
+  // TODO(rjshade): Remove when removing QUIC_VERSION_19.
+  static void SetReceivedInitialFlowControlWindow(QuicConfig* config,
+                                                  uint32 window_bytes);
+
+  static void SetReceivedInitialStreamFlowControlWindow(QuicConfig* config,
+                                                        uint32 window_bytes);
+
+  static void SetReceivedInitialSessionFlowControlWindow(QuicConfig* config,
+                                                         uint32 window_bytes);
+
+  static void SetReceivedConnectionOptions(QuicConfig* config,
+                                           const QuicTagVector& options);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicConfigPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_CONFIG_PEER_H_
diff --git a/net/quic/test_tools/quic_connection_peer.cc b/net/quic/test_tools/quic_connection_peer.cc
new file mode 100644
index 0000000..a148249
--- /dev/null
+++ b/net/quic/test_tools/quic_connection_peer.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_connection_peer.h"
+
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_packet_writer.h"
+#include "net/quic/quic_received_packet_manager.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_packet_generator_peer.h"
+#include "net/quic/test_tools/quic_sent_packet_manager_peer.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicConnectionPeer::SendAck(QuicConnection* connection) {
+  connection->SendAck();
+}
+
+// static
+void QuicConnectionPeer::SetReceiveAlgorithm(
+    QuicConnection* connection,
+    ReceiveAlgorithmInterface* receive_algorithm) {
+  connection->received_packet_manager_.receive_algorithm_.reset(
+      receive_algorithm);
+}
+
+// static
+void QuicConnectionPeer::SetSendAlgorithm(
+    QuicConnection* connection,
+    SendAlgorithmInterface* send_algorithm) {
+  connection->sent_packet_manager_.send_algorithm_.reset(send_algorithm);
+}
+
+// static
+QuicAckFrame* QuicConnectionPeer::CreateAckFrame(QuicConnection* connection) {
+  return connection->CreateAckFrame();
+}
+
+// static
+QuicStopWaitingFrame* QuicConnectionPeer::CreateStopWaitingFrame(
+    QuicConnection* connection) {
+  return connection->CreateStopWaitingFrame();
+}
+
+// static
+QuicConnectionVisitorInterface* QuicConnectionPeer::GetVisitor(
+    QuicConnection* connection) {
+  return connection->visitor_;
+}
+
+// static
+QuicPacketCreator* QuicConnectionPeer::GetPacketCreator(
+    QuicConnection* connection) {
+  return QuicPacketGeneratorPeer::GetPacketCreator(
+      &connection->packet_generator_);
+}
+
+// static
+QuicPacketGenerator* QuicConnectionPeer::GetPacketGenerator(
+    QuicConnection* connection) {
+  return &connection->packet_generator_;
+}
+
+// static
+QuicSentPacketManager* QuicConnectionPeer::GetSentPacketManager(
+    QuicConnection* connection) {
+  return &connection->sent_packet_manager_;
+}
+
+// static
+QuicReceivedPacketManager* QuicConnectionPeer::GetReceivedPacketManager(
+    QuicConnection* connection) {
+  return &connection->received_packet_manager_;
+}
+
+// static
+QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout(
+    QuicConnection* connection) {
+  return connection->idle_network_timeout_;
+}
+
+// static
+bool QuicConnectionPeer::IsSavedForRetransmission(
+    QuicConnection* connection,
+    QuicPacketSequenceNumber sequence_number) {
+  return connection->sent_packet_manager_.IsUnacked(sequence_number) &&
+      connection->sent_packet_manager_.HasRetransmittableFrames(
+          sequence_number);
+}
+
+// static
+bool QuicConnectionPeer::IsRetransmission(
+    QuicConnection* connection,
+    QuicPacketSequenceNumber sequence_number) {
+  return QuicSentPacketManagerPeer::IsRetransmission(
+      &connection->sent_packet_manager_, sequence_number);
+}
+
+// static
+// TODO(ianswett): Create a GetSentEntropyHash which accepts an AckFrame.
+QuicPacketEntropyHash QuicConnectionPeer::GetSentEntropyHash(
+    QuicConnection* connection,
+    QuicPacketSequenceNumber sequence_number) {
+  QuicSentEntropyManager::CumulativeEntropy last_entropy_copy =
+      connection->sent_entropy_manager_.last_cumulative_entropy_;
+  connection->sent_entropy_manager_.UpdateCumulativeEntropy(sequence_number,
+                                                            &last_entropy_copy);
+  return last_entropy_copy.entropy;
+}
+
+// static
+QuicPacketEntropyHash QuicConnectionPeer::PacketEntropy(
+    QuicConnection* connection,
+    QuicPacketSequenceNumber sequence_number) {
+  return connection->sent_entropy_manager_.GetPacketEntropy(sequence_number);
+}
+
+// static
+QuicPacketEntropyHash QuicConnectionPeer::ReceivedEntropyHash(
+    QuicConnection* connection,
+    QuicPacketSequenceNumber sequence_number) {
+  return connection->received_packet_manager_.EntropyHash(
+      sequence_number);
+}
+
+// static
+bool QuicConnectionPeer::IsServer(QuicConnection* connection) {
+  return connection->is_server_;
+}
+
+// static
+void QuicConnectionPeer::SetIsServer(QuicConnection* connection,
+                                     bool is_server) {
+  connection->is_server_ = is_server;
+  QuicFramerPeer::SetIsServer(&connection->framer_, is_server);
+}
+
+// static
+void QuicConnectionPeer::SetSelfAddress(QuicConnection* connection,
+                                        const IPEndPoint& self_address) {
+  connection->self_address_ = self_address;
+}
+
+// static
+void QuicConnectionPeer::SetPeerAddress(QuicConnection* connection,
+                                        const IPEndPoint& peer_address) {
+  connection->peer_address_ = peer_address;
+}
+
+// static
+void QuicConnectionPeer::SwapCrypters(QuicConnection* connection,
+                                      QuicFramer* framer) {
+  QuicFramerPeer::SwapCrypters(framer, &connection->framer_);
+}
+
+// static
+QuicConnectionHelperInterface* QuicConnectionPeer::GetHelper(
+    QuicConnection* connection) {
+  return connection->helper_;
+}
+
+// static
+QuicFramer* QuicConnectionPeer::GetFramer(QuicConnection* connection) {
+  return &connection->framer_;
+}
+
+// static
+QuicFecGroup* QuicConnectionPeer::GetFecGroup(QuicConnection* connection,
+                                              int fec_group) {
+  connection->last_header_.fec_group = fec_group;
+  return connection->GetFecGroup();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetAckAlarm(QuicConnection* connection) {
+  return connection->ack_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetPingAlarm(QuicConnection* connection) {
+  return connection->ping_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetResumeWritesAlarm(
+    QuicConnection* connection) {
+  return connection->resume_writes_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetRetransmissionAlarm(
+    QuicConnection* connection) {
+  return connection->retransmission_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetSendAlarm(QuicConnection* connection) {
+  return connection->send_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetTimeoutAlarm(QuicConnection* connection) {
+  return connection->timeout_alarm_.get();
+}
+
+// static
+QuicPacketWriter* QuicConnectionPeer::GetWriter(QuicConnection* connection) {
+  return connection->writer_;
+}
+
+// static
+void QuicConnectionPeer::SetWriter(QuicConnection* connection,
+                                   QuicPacketWriter* writer,
+                                   bool owns_writer) {
+  if (connection->owns_writer_) {
+    delete connection->writer_;
+  }
+  connection->writer_ = writer;
+  connection->owns_writer_ = owns_writer;
+}
+
+// static
+void QuicConnectionPeer::CloseConnection(QuicConnection* connection) {
+  connection->connected_ = false;
+}
+
+// static
+QuicEncryptedPacket* QuicConnectionPeer::GetConnectionClosePacket(
+    QuicConnection* connection) {
+  return connection->connection_close_packet_.get();
+}
+
+// static
+void QuicConnectionPeer::SetSupportedVersions(QuicConnection* connection,
+                                              QuicVersionVector versions) {
+  connection->framer_.SetSupportedVersions(versions);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_connection_peer.h b/net/quic/test_tools/quic_connection_peer.h
new file mode 100644
index 0000000..c9c4ee7
--- /dev/null
+++ b/net/quic/test_tools/quic_connection_peer.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CONNECTION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CONNECTION_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_connection_stats.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+struct QuicAckFrame;
+struct QuicPacketHeader;
+class QuicAlarm;
+class QuicConnection;
+class QuicConnectionHelperInterface;
+class QuicConnectionVisitorInterface;
+class QuicEncryptedPacket;
+class QuicFecGroup;
+class QuicFramer;
+class QuicPacketCreator;
+class QuicPacketGenerator;
+class QuicPacketWriter;
+class QuicReceivedPacketManager;
+class QuicSentPacketManager;
+class ReceiveAlgorithmInterface;
+class SendAlgorithmInterface;
+
+namespace test {
+
+// Peer to make public a number of otherwise private QuicConnection methods.
+class QuicConnectionPeer {
+ public:
+  static void SendAck(QuicConnection* connection);
+
+  static void SetReceiveAlgorithm(QuicConnection* connection,
+                                  ReceiveAlgorithmInterface* receive_algorithm);
+
+  static void SetSendAlgorithm(QuicConnection* connection,
+                               SendAlgorithmInterface* send_algorithm);
+
+  static QuicAckFrame* CreateAckFrame(QuicConnection* connection);
+
+  static QuicStopWaitingFrame* CreateStopWaitingFrame(
+      QuicConnection* connection);
+
+  static QuicConnectionVisitorInterface* GetVisitor(
+      QuicConnection* connection);
+
+  static QuicPacketCreator* GetPacketCreator(QuicConnection* connection);
+
+  static QuicPacketGenerator* GetPacketGenerator(QuicConnection* connection);
+
+  static QuicSentPacketManager* GetSentPacketManager(
+      QuicConnection* connection);
+
+  static QuicReceivedPacketManager* GetReceivedPacketManager(
+      QuicConnection* connection);
+
+  static QuicTime::Delta GetNetworkTimeout(QuicConnection* connection);
+
+  static bool IsSavedForRetransmission(
+      QuicConnection* connection,
+      QuicPacketSequenceNumber sequence_number);
+
+  static bool IsRetransmission(QuicConnection* connection,
+                               QuicPacketSequenceNumber sequence_number);
+
+  static QuicPacketEntropyHash GetSentEntropyHash(
+      QuicConnection* connection,
+      QuicPacketSequenceNumber sequence_number);
+
+  static QuicPacketEntropyHash PacketEntropy(
+      QuicConnection* connection,
+      QuicPacketSequenceNumber sequence_number);
+
+  static QuicPacketEntropyHash ReceivedEntropyHash(
+      QuicConnection* connection,
+      QuicPacketSequenceNumber sequence_number);
+
+  static bool IsServer(QuicConnection* connection);
+
+  static void SetIsServer(QuicConnection* connection, bool is_server);
+
+  static void SetSelfAddress(QuicConnection* connection,
+                             const IPEndPoint& self_address);
+
+  static void SetPeerAddress(QuicConnection* connection,
+                             const IPEndPoint& peer_address);
+
+  static void SwapCrypters(QuicConnection* connection, QuicFramer* framer);
+
+  static QuicConnectionHelperInterface* GetHelper(QuicConnection* connection);
+
+  static QuicFramer* GetFramer(QuicConnection* connection);
+
+  // Set last_header_->fec_group = fec_group and return connection->GetFecGroup
+  static QuicFecGroup* GetFecGroup(QuicConnection* connection, int fec_group);
+
+  static QuicAlarm* GetAckAlarm(QuicConnection* connection);
+  static QuicAlarm* GetPingAlarm(QuicConnection* connection);
+  static QuicAlarm* GetResumeWritesAlarm(QuicConnection* connection);
+  static QuicAlarm* GetRetransmissionAlarm(QuicConnection* connection);
+  static QuicAlarm* GetSendAlarm(QuicConnection* connection);
+  static QuicAlarm* GetTimeoutAlarm(QuicConnection* connection);
+
+  static QuicPacketWriter* GetWriter(QuicConnection* connection);
+  // If |owns_writer| is true, takes ownership of |writer|.
+  static void SetWriter(QuicConnection* connection,
+                        QuicPacketWriter* writer,
+                        bool owns_writer);
+  static void CloseConnection(QuicConnection* connection);
+  static QuicEncryptedPacket* GetConnectionClosePacket(
+      QuicConnection* connection);
+
+  static void SetSupportedVersions(QuicConnection* connection,
+                                   QuicVersionVector versions);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicConnectionPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_CONNECTION_PEER_H_
diff --git a/net/quic/test_tools/quic_data_stream_peer.cc b/net/quic/test_tools/quic_data_stream_peer.cc
new file mode 100644
index 0000000..6165675
--- /dev/null
+++ b/net/quic/test_tools/quic_data_stream_peer.cc
@@ -0,0 +1,19 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_data_stream_peer.h"
+
+#include "net/quic/quic_data_stream.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicDataStreamPeer::SetHeadersDecompressed(QuicDataStream* stream,
+                                                bool headers_decompressed) {
+  stream->headers_decompressed_ = headers_decompressed;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_data_stream_peer.h b/net/quic/test_tools/quic_data_stream_peer.h
new file mode 100644
index 0000000..914b87d
--- /dev/null
+++ b/net/quic/test_tools/quic_data_stream_peer.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_DATA_STREAM_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_DATA_STREAM_PEER_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+class QuicDataStream;
+
+namespace test {
+
+class QuicDataStreamPeer {
+ public:
+  static void SetHeadersDecompressed(QuicDataStream* stream,
+                                     bool headers_decompressed);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicDataStreamPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_DATA_STREAM_PEER_H_
diff --git a/net/quic/test_tools/quic_flow_controller_peer.cc b/net/quic/test_tools/quic_flow_controller_peer.cc
new file mode 100644
index 0000000..35882ed
--- /dev/null
+++ b/net/quic/test_tools/quic_flow_controller_peer.cc
@@ -0,0 +1,61 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_flow_controller_peer.h"
+
+#include <list>
+
+#include "net/quic/quic_flow_controller.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicFlowControllerPeer::SetSendWindowOffset(
+    QuicFlowController* flow_controller,
+    uint64 offset) {
+  flow_controller->send_window_offset_ = offset;
+}
+
+// static
+void QuicFlowControllerPeer::SetReceiveWindowOffset(
+    QuicFlowController* flow_controller,
+    uint64 offset) {
+  flow_controller->receive_window_offset_ = offset;
+}
+
+// static
+void QuicFlowControllerPeer::SetMaxReceiveWindow(
+    QuicFlowController* flow_controller, uint64 window_size) {
+  flow_controller->max_receive_window_ = window_size;
+}
+
+// static
+uint64 QuicFlowControllerPeer::SendWindowOffset(
+    QuicFlowController* flow_controller) {
+  return flow_controller->send_window_offset_;
+}
+
+// static
+uint64 QuicFlowControllerPeer::SendWindowSize(
+    QuicFlowController* flow_controller) {
+  return flow_controller->SendWindowSize();
+}
+
+// static
+uint64 QuicFlowControllerPeer::ReceiveWindowOffset(
+    QuicFlowController* flow_controller) {
+  return flow_controller->receive_window_offset_;
+}
+
+// static
+uint64 QuicFlowControllerPeer::ReceiveWindowSize(
+    QuicFlowController* flow_controller) {
+  return flow_controller->receive_window_offset_ -
+         flow_controller->highest_received_byte_offset_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_flow_controller_peer.h b/net/quic/test_tools/quic_flow_controller_peer.h
new file mode 100644
index 0000000..213d40d
--- /dev/null
+++ b/net/quic/test_tools/quic_flow_controller_peer.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_FLOW_CONTROLLER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_FLOW_CONTROLLER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicFlowController;
+
+namespace test {
+
+class QuicFlowControllerPeer {
+ public:
+  static void SetSendWindowOffset(QuicFlowController* flow_controller,
+                                  uint64 offset);
+
+  static void SetReceiveWindowOffset(QuicFlowController* flow_controller,
+                                     uint64 offset);
+
+  static void SetMaxReceiveWindow(QuicFlowController* flow_controller,
+                                  uint64 window_size);
+
+  static uint64 SendWindowOffset(QuicFlowController* flow_controller);
+
+  static uint64 SendWindowSize(QuicFlowController* flow_controller);
+
+  static uint64 ReceiveWindowOffset(QuicFlowController* flow_controller);
+
+  static uint64 ReceiveWindowSize(QuicFlowController* flow_controller);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicFlowControllerPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_FLOW_CONTROLLER_PEER_H_
diff --git a/net/quic/test_tools/quic_framer_peer.cc b/net/quic/test_tools/quic_framer_peer.cc
new file mode 100644
index 0000000..c6464cc
--- /dev/null
+++ b/net/quic/test_tools/quic_framer_peer.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_framer_peer.h"
+
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+
+// static
+QuicPacketSequenceNumber QuicFramerPeer::CalculatePacketSequenceNumberFromWire(
+    QuicFramer* framer,
+    QuicSequenceNumberLength sequence_number_length,
+    QuicPacketSequenceNumber packet_sequence_number) {
+  return framer->CalculatePacketSequenceNumberFromWire(sequence_number_length,
+                                                       packet_sequence_number);
+}
+
+// static
+void QuicFramerPeer::SetLastSerializedConnectionId(
+    QuicFramer* framer, QuicConnectionId connection_id) {
+  framer->last_serialized_connection_id_ = connection_id;
+}
+
+void QuicFramerPeer::SetLastSequenceNumber(
+    QuicFramer* framer,
+    QuicPacketSequenceNumber packet_sequence_number) {
+  framer->last_sequence_number_ = packet_sequence_number;
+}
+
+void QuicFramerPeer::SetIsServer(QuicFramer* framer, bool is_server) {
+  framer->is_server_ = is_server;
+}
+
+void QuicFramerPeer::SwapCrypters(QuicFramer* framer1, QuicFramer* framer2) {
+  for (int i = ENCRYPTION_NONE; i < NUM_ENCRYPTION_LEVELS; i++) {
+    framer1->encrypter_[i].swap(framer2->encrypter_[i]);
+  }
+  framer1->decrypter_.swap(framer2->decrypter_);
+  framer1->alternative_decrypter_.swap(framer2->alternative_decrypter_);
+
+  EncryptionLevel framer2_level = framer2->decrypter_level_;
+  framer2->decrypter_level_ = framer1->decrypter_level_;
+  framer1->decrypter_level_ = framer2_level;
+  framer2_level = framer2->alternative_decrypter_level_;
+  framer2->alternative_decrypter_level_ =
+      framer1->alternative_decrypter_level_;
+  framer1->alternative_decrypter_level_ = framer2_level;
+
+  const bool framer2_latch = framer2->alternative_decrypter_latch_;
+  framer2->alternative_decrypter_latch_ =
+      framer1->alternative_decrypter_latch_;
+  framer1->alternative_decrypter_latch_ = framer2_latch;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_framer_peer.h b/net/quic/test_tools/quic_framer_peer.h
new file mode 100644
index 0000000..9b9c9fa
--- /dev/null
+++ b/net/quic/test_tools/quic_framer_peer.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicFramer;
+
+namespace test {
+
+class QuicFramerPeer {
+ public:
+  static QuicPacketSequenceNumber CalculatePacketSequenceNumberFromWire(
+      QuicFramer* framer,
+      QuicSequenceNumberLength sequence_number_length,
+      QuicPacketSequenceNumber packet_sequence_number);
+  static void SetLastSerializedConnectionId(QuicFramer* framer,
+                                            QuicConnectionId connection_id);
+  static void SetLastSequenceNumber(
+      QuicFramer* framer,
+      QuicPacketSequenceNumber packet_sequence_number);
+  static void SetIsServer(QuicFramer* framer, bool is_server);
+
+  // SwapCrypters exchanges the state of the crypters of |framer1| with
+  // |framer2|.
+  static void SwapCrypters(QuicFramer* framer1, QuicFramer* framer2);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicFramerPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
diff --git a/net/quic/test_tools/quic_packet_creator_peer.cc b/net/quic/test_tools/quic_packet_creator_peer.cc
new file mode 100644
index 0000000..a801447
--- /dev/null
+++ b/net/quic/test_tools/quic_packet_creator_peer.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+
+#include "net/quic/quic_packet_creator.h"
+
+namespace net {
+namespace test {
+
+// static
+bool QuicPacketCreatorPeer::SendVersionInPacket(QuicPacketCreator* creator) {
+  return creator->send_version_in_packet_;
+}
+
+// static
+void QuicPacketCreatorPeer::SetSendVersionInPacket(
+    QuicPacketCreator* creator, bool send_version_in_packet) {
+  creator->send_version_in_packet_ = send_version_in_packet;
+}
+
+// static
+void QuicPacketCreatorPeer::SetSequenceNumberLength(
+      QuicPacketCreator* creator,
+      QuicSequenceNumberLength sequence_number_length) {
+  creator->sequence_number_length_ = sequence_number_length;
+}
+
+// static
+QuicSequenceNumberLength QuicPacketCreatorPeer::GetSequenceNumberLength(
+    QuicPacketCreator* creator) {
+  return creator->sequence_number_length_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_packet_creator_peer.h b/net/quic/test_tools/quic_packet_creator_peer.h
new file mode 100644
index 0000000..b4bf8a7
--- /dev/null
+++ b/net/quic/test_tools/quic_packet_creator_peer.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+class QuicPacketCreator;
+
+namespace test {
+
+class QuicPacketCreatorPeer {
+ public:
+  static bool SendVersionInPacket(QuicPacketCreator* creator);
+
+  static void SetSendVersionInPacket(QuicPacketCreator* creator,
+                                     bool send_version_in_packet);
+  static void SetSequenceNumberLength(
+      QuicPacketCreator* creator,
+      QuicSequenceNumberLength sequence_number_length);
+  static QuicSequenceNumberLength GetSequenceNumberLength(
+      QuicPacketCreator* creator);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicPacketCreatorPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
diff --git a/net/quic/test_tools/quic_packet_generator_peer.cc b/net/quic/test_tools/quic_packet_generator_peer.cc
new file mode 100644
index 0000000..ad0693a
--- /dev/null
+++ b/net/quic/test_tools/quic_packet_generator_peer.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_packet_generator_peer.h"
+
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_packet_generator.h"
+
+namespace net {
+namespace test {
+
+// static
+QuicPacketCreator* QuicPacketGeneratorPeer::GetPacketCreator(
+    QuicPacketGenerator* generator) {
+  return &generator->packet_creator_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_packet_generator_peer.h b/net/quic/test_tools/quic_packet_generator_peer.h
new file mode 100644
index 0000000..0762d1f
--- /dev/null
+++ b/net/quic/test_tools/quic_packet_generator_peer.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_PACKET_GENERATOR_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_PACKET_GENERATOR_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicPacketCreator;
+class QuicPacketGenerator;
+
+namespace test {
+
+class QuicPacketGeneratorPeer {
+ public:
+  static QuicPacketCreator* GetPacketCreator(QuicPacketGenerator* generator);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicPacketGeneratorPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_PACKET_GENERATOR_PEER_H_
diff --git a/net/quic/test_tools/quic_received_packet_manager_peer.cc b/net/quic/test_tools/quic_received_packet_manager_peer.cc
new file mode 100644
index 0000000..2faf84e
--- /dev/null
+++ b/net/quic/test_tools/quic_received_packet_manager_peer.cc
@@ -0,0 +1,30 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_received_packet_manager_peer.h"
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_received_packet_manager.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicReceivedPacketManagerPeer::SetCumulativeEntropyUpTo(
+    QuicReceivedPacketManager* received_packet_manager,
+    QuicPacketSequenceNumber peer_least_unacked,
+    QuicPacketEntropyHash entropy_hash) {
+  received_packet_manager->entropy_tracker_.SetCumulativeEntropyUpTo(
+      peer_least_unacked, entropy_hash);
+}
+
+// static
+bool QuicReceivedPacketManagerPeer::DontWaitForPacketsBefore(
+    QuicReceivedPacketManager* received_packet_manager,
+    QuicPacketSequenceNumber least_unacked) {
+  return received_packet_manager->DontWaitForPacketsBefore(least_unacked);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_received_packet_manager_peer.h b/net/quic/test_tools/quic_received_packet_manager_peer.h
new file mode 100644
index 0000000..c3cb2d3
--- /dev/null
+++ b/net/quic/test_tools/quic_received_packet_manager_peer.h
@@ -0,0 +1,35 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicReceivedPacketManager;
+
+namespace test {
+
+class QuicReceivedPacketManagerPeer {
+ public:
+  static void SetCumulativeEntropyUpTo(
+      QuicReceivedPacketManager* received_packet_manager,
+      QuicPacketSequenceNumber peer_least_unacked,
+      QuicPacketEntropyHash entropy_hash);
+
+  static bool DontWaitForPacketsBefore(
+      QuicReceivedPacketManager* received_packet_manager,
+      QuicPacketSequenceNumber least_unacked);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicReceivedPacketManagerPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
diff --git a/net/quic/test_tools/quic_sent_packet_manager_peer.cc b/net/quic/test_tools/quic_sent_packet_manager_peer.cc
new file mode 100644
index 0000000..bebd554
--- /dev/null
+++ b/net/quic/test_tools/quic_sent_packet_manager_peer.cc
@@ -0,0 +1,157 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_sent_packet_manager_peer.h"
+
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_sent_packet_manager.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicSentPacketManagerPeer::SetMaxTailLossProbes(
+    QuicSentPacketManager* sent_packet_manager, size_t max_tail_loss_probes) {
+  sent_packet_manager->max_tail_loss_probes_ = max_tail_loss_probes;
+}
+
+// static
+void QuicSentPacketManagerPeer::SetSendAlgorithm(
+    QuicSentPacketManager* sent_packet_manager,
+    SendAlgorithmInterface* send_algorithm) {
+  sent_packet_manager->send_algorithm_.reset(send_algorithm);
+}
+
+// static
+void QuicSentPacketManagerPeer::SetIsServer(
+    QuicSentPacketManager* sent_packet_manager,
+    bool is_server) {
+  sent_packet_manager->is_server_ = is_server;
+}
+
+// static
+const LossDetectionInterface* QuicSentPacketManagerPeer::GetLossAlgorithm(
+    QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->loss_algorithm_.get();
+}
+
+// static
+const SendAlgorithmInterface*
+    QuicSentPacketManagerPeer::GetCongestionControlAlgorithm(
+    const QuicSentPacketManager& sent_packet_manager) {
+  return sent_packet_manager.send_algorithm_.get();
+}
+
+// static
+void QuicSentPacketManagerPeer::SetLossAlgorithm(
+    QuicSentPacketManager* sent_packet_manager,
+    LossDetectionInterface* loss_detector) {
+  sent_packet_manager->loss_algorithm_.reset(loss_detector);
+}
+
+// static
+RttStats* QuicSentPacketManagerPeer::GetRttStats(
+    QuicSentPacketManager* sent_packet_manager) {
+  return &sent_packet_manager->rtt_stats_;
+}
+
+// static
+size_t QuicSentPacketManagerPeer::GetNackCount(
+    const QuicSentPacketManager* sent_packet_manager,
+    QuicPacketSequenceNumber sequence_number) {
+  return sent_packet_manager->unacked_packets_.
+      GetTransmissionInfo(sequence_number).nack_count;
+}
+
+// static
+size_t QuicSentPacketManagerPeer::GetPendingRetransmissionCount(
+    const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->pending_retransmissions_.size();
+}
+
+// static
+bool QuicSentPacketManagerPeer::HasPendingPackets(
+    const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->unacked_packets_.HasInFlightPackets();
+}
+
+// static
+QuicTime QuicSentPacketManagerPeer::GetSentTime(
+    const QuicSentPacketManager* sent_packet_manager,
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK(sent_packet_manager->unacked_packets_.IsUnacked(sequence_number));
+
+  return sent_packet_manager->unacked_packets_.GetTransmissionInfo(
+      sequence_number).sent_time;
+}
+
+// static
+bool QuicSentPacketManagerPeer::IsRetransmission(
+    QuicSentPacketManager* sent_packet_manager,
+    QuicPacketSequenceNumber sequence_number) {
+  DCHECK(sent_packet_manager->HasRetransmittableFrames(sequence_number));
+  return sent_packet_manager->HasRetransmittableFrames(sequence_number) &&
+      sent_packet_manager->unacked_packets_.GetTransmissionInfo(
+          sequence_number).all_transmissions != nullptr;
+}
+
+// static
+void QuicSentPacketManagerPeer::MarkForRetransmission(
+    QuicSentPacketManager* sent_packet_manager,
+    QuicPacketSequenceNumber sequence_number,
+    TransmissionType transmission_type) {
+  sent_packet_manager->MarkForRetransmission(sequence_number,
+                                             transmission_type);
+}
+
+// static
+QuicTime::Delta QuicSentPacketManagerPeer::GetRetransmissionDelay(
+    const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->GetRetransmissionDelay();
+}
+
+// static
+bool QuicSentPacketManagerPeer::HasUnackedCryptoPackets(
+    const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->unacked_packets_.HasPendingCryptoPackets();
+}
+
+// static
+size_t QuicSentPacketManagerPeer::GetNumRetransmittablePackets(
+    const QuicSentPacketManager* sent_packet_manager) {
+  size_t num_unacked_packets = 0;
+  for (QuicUnackedPacketMap::const_iterator it =
+           sent_packet_manager->unacked_packets_.begin();
+       it != sent_packet_manager->unacked_packets_.end(); ++it) {
+    if (it->retransmittable_frames != nullptr) {
+      ++num_unacked_packets;
+    }
+  }
+  return num_unacked_packets;
+}
+
+// static
+QuicByteCount QuicSentPacketManagerPeer::GetBytesInFlight(
+    const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->unacked_packets_.bytes_in_flight();
+}
+
+// static
+QuicSentPacketManager::NetworkChangeVisitor*
+    QuicSentPacketManagerPeer::GetNetworkChangeVisitor(
+        const QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->network_change_visitor_;
+}
+
+// static
+QuicSustainedBandwidthRecorder& QuicSentPacketManagerPeer::GetBandwidthRecorder(
+    QuicSentPacketManager* sent_packet_manager) {
+  return sent_packet_manager->sustained_bandwidth_recorder_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_sent_packet_manager_peer.h b/net/quic/test_tools/quic_sent_packet_manager_peer.h
new file mode 100644
index 0000000..b4c6857
--- /dev/null
+++ b/net/quic/test_tools/quic_sent_packet_manager_peer.h
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_SENT_PACKET_MANAGER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_SENT_PACKET_MANAGER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_sent_packet_manager.h"
+
+namespace net {
+
+class SendAlgorithmInterface;
+
+namespace test {
+
+class QuicSentPacketManagerPeer {
+ public:
+  static void SetMaxTailLossProbes(
+      QuicSentPacketManager* sent_packet_manager, size_t max_tail_loss_probes);
+
+  static void SetSendAlgorithm(QuicSentPacketManager* sent_packet_manager,
+                               SendAlgorithmInterface* send_algorithm);
+
+  static void SetIsServer(QuicSentPacketManager* sent_packet_manager,
+                          bool is_server);
+
+  static const LossDetectionInterface* GetLossAlgorithm(
+      QuicSentPacketManager* sent_packet_manager);
+
+  static const SendAlgorithmInterface* GetCongestionControlAlgorithm(
+      const QuicSentPacketManager& sent_packet_manager);
+
+  static void SetLossAlgorithm(QuicSentPacketManager* sent_packet_manager,
+                               LossDetectionInterface* loss_detector);
+
+  static RttStats* GetRttStats(QuicSentPacketManager* sent_packet_manager);
+
+  static size_t GetNackCount(
+      const QuicSentPacketManager* sent_packet_manager,
+      QuicPacketSequenceNumber sequence_number);
+
+  static size_t GetPendingRetransmissionCount(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static bool HasPendingPackets(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static QuicTime GetSentTime(const QuicSentPacketManager* sent_packet_manager,
+                              QuicPacketSequenceNumber sequence_number);
+
+  // Returns true if |sequence_number| is a retransmission of a packet.
+  static bool IsRetransmission(QuicSentPacketManager* sent_packet_manager,
+                               QuicPacketSequenceNumber sequence_number);
+
+  static void MarkForRetransmission(QuicSentPacketManager* sent_packet_manager,
+                                    QuicPacketSequenceNumber sequence_number,
+                                    TransmissionType transmission_type);
+
+  static QuicTime::Delta GetRetransmissionDelay(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static bool HasUnackedCryptoPackets(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static size_t GetNumRetransmittablePackets(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static QuicByteCount GetBytesInFlight(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static QuicSentPacketManager::NetworkChangeVisitor* GetNetworkChangeVisitor(
+      const QuicSentPacketManager* sent_packet_manager);
+
+  static QuicSustainedBandwidthRecorder& GetBandwidthRecorder(
+      QuicSentPacketManager* sent_packet_manager);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicSentPacketManagerPeer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_SENT_PACKET_MANAGER_PEER_H_
diff --git a/net/quic/test_tools/quic_session_peer.cc b/net/quic/test_tools/quic_session_peer.cc
new file mode 100644
index 0000000..14897b2
--- /dev/null
+++ b/net/quic/test_tools/quic_session_peer.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_session_peer.h"
+
+#include "net/quic/quic_session.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicSessionPeer::SetNextStreamId(QuicSession* session, QuicStreamId id) {
+  session->next_stream_id_ = id;
+}
+
+// static
+void QuicSessionPeer::SetMaxOpenStreams(QuicSession* session,
+                                        uint32 max_streams) {
+  session->max_open_streams_ = max_streams;
+}
+
+// static
+QuicCryptoStream* QuicSessionPeer::GetCryptoStream(QuicSession* session) {
+  return session->GetCryptoStream();
+}
+
+// static
+QuicHeadersStream* QuicSessionPeer::GetHeadersStream(QuicSession* session) {
+  return session->headers_stream_.get();
+}
+
+// static
+void QuicSessionPeer::SetHeadersStream(QuicSession* session,
+                                       QuicHeadersStream* headers_stream) {
+  session->headers_stream_.reset(headers_stream);
+}
+
+// static
+QuicWriteBlockedList* QuicSessionPeer::GetWriteBlockedStreams(
+    QuicSession* session) {
+  return &session->write_blocked_streams_;
+}
+
+// static
+QuicDataStream* QuicSessionPeer::GetIncomingDataStream(
+    QuicSession* session,
+    QuicStreamId stream_id) {
+  return session->GetIncomingDataStream(stream_id);
+}
+
+// static
+map<QuicStreamId, QuicStreamOffset>&
+QuicSessionPeer::GetLocallyClosedStreamsHighestOffset(QuicSession* session) {
+  return session->locally_closed_streams_highest_offset_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_session_peer.h b/net/quic/test_tools/quic_session_peer.h
new file mode 100644
index 0000000..e69f6ba
--- /dev/null
+++ b/net/quic/test_tools/quic_session_peer.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_write_blocked_list.h"
+
+namespace net {
+
+class QuicCryptoStream;
+class QuicDataStream;
+class QuicHeadersStream;
+class QuicSession;
+
+namespace test {
+
+class QuicSessionPeer {
+ public:
+  static void SetNextStreamId(QuicSession* session, QuicStreamId id);
+  static void SetMaxOpenStreams(QuicSession* session, uint32 max_streams);
+  static QuicCryptoStream* GetCryptoStream(QuicSession* session);
+  static QuicHeadersStream* GetHeadersStream(QuicSession* session);
+  static void SetHeadersStream(QuicSession* session,
+                               QuicHeadersStream* headers_stream);
+  static QuicWriteBlockedList* GetWriteBlockedStreams(QuicSession* session);
+  static QuicDataStream* GetIncomingDataStream(QuicSession* session,
+                                               QuicStreamId stream_id);
+  static std::map<QuicStreamId, QuicStreamOffset>&
+  GetLocallyClosedStreamsHighestOffset(QuicSession* session);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicSessionPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
diff --git a/net/quic/test_tools/quic_stream_sequencer_peer.cc b/net/quic/test_tools/quic_stream_sequencer_peer.cc
new file mode 100644
index 0000000..08a5c5b
--- /dev/null
+++ b/net/quic/test_tools/quic_stream_sequencer_peer.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_stream_sequencer_peer.h"
+
+#include "net/quic/quic_stream_sequencer.h"
+
+using std::map;
+using std::string;
+
+namespace net {
+namespace test {
+
+// static
+map<QuicStreamOffset, string>* QuicStreamSequencerPeer::GetBufferedFrames(
+    QuicStreamSequencer* sequencer) {
+  return &(sequencer->buffered_frames_);
+}
+
+// static
+QuicStreamOffset QuicStreamSequencerPeer::GetCloseOffset(
+    QuicStreamSequencer* sequencer) {
+  return sequencer->close_offset_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_stream_sequencer_peer.h b/net/quic/test_tools/quic_stream_sequencer_peer.h
new file mode 100644
index 0000000..7b2b28a
--- /dev/null
+++ b/net/quic/test_tools/quic_stream_sequencer_peer.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_STREAM_SEQUENCER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_STREAM_SEQUENCER_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicStreamSequencer;
+
+namespace test {
+
+class QuicStreamSequencerPeer {
+ public:
+  static std::map<QuicStreamOffset, std::string>* GetBufferedFrames(
+      QuicStreamSequencer* sequencer);
+
+  static QuicStreamOffset GetCloseOffset(QuicStreamSequencer* sequencer);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicStreamSequencerPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_STREAM_SEQUENCER_PEER_H_
diff --git a/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.cc b/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.cc
new file mode 100644
index 0000000..fd3678c
--- /dev/null
+++ b/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.cc
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h"
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_sustained_bandwidth_recorder.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicSustainedBandwidthRecorderPeer::SetBandwidthEstimate(
+    QuicSustainedBandwidthRecorder* bandwidth_recorder,
+    int32 bandwidth_estimate_kbytes_per_second) {
+  bandwidth_recorder->has_estimate_ = true;
+  bandwidth_recorder->bandwidth_estimate_ =
+      QuicBandwidth::FromKBytesPerSecond(bandwidth_estimate_kbytes_per_second);
+}
+
+// static
+void QuicSustainedBandwidthRecorderPeer::SetMaxBandwidthEstimate(
+    QuicSustainedBandwidthRecorder* bandwidth_recorder,
+    int32 max_bandwidth_estimate_kbytes_per_second,
+    int32 max_bandwidth_timestamp) {
+  bandwidth_recorder->max_bandwidth_estimate_ =
+      QuicBandwidth::FromKBytesPerSecond(
+          max_bandwidth_estimate_kbytes_per_second);
+  bandwidth_recorder->max_bandwidth_timestamp_ = max_bandwidth_timestamp;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h b/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h
new file mode 100644
index 0000000..161a94e
--- /dev/null
+++ b/net/quic/test_tools/quic_sustained_bandwidth_recorder_peer.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_SUSTAINED_BANDWIDTH_RECORDER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_SUSTAINED_BANDWIDTH_RECORDER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicSustainedBandwidthRecorder;
+
+namespace test {
+
+class QuicSustainedBandwidthRecorderPeer {
+ public:
+  static void SetBandwidthEstimate(
+      QuicSustainedBandwidthRecorder* bandwidth_recorder,
+      int32 bandwidth_estimate_kbytes_per_second);
+
+  static void SetMaxBandwidthEstimate(
+      QuicSustainedBandwidthRecorder* bandwidth_recorder,
+      int32 max_bandwidth_estimate_kbytes_per_second,
+      int32 max_bandwidth_timestamp);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(QuicSustainedBandwidthRecorderPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_SUSTAINED_BANDWIDTH_RECORDER_PEER_H_
diff --git a/net/quic/test_tools/quic_test_packet_maker.cc b/net/quic/test_tools/quic_test_packet_maker.cc
new file mode 100644
index 0000000..6540426
--- /dev/null
+++ b/net/quic/test_tools/quic_test_packet_maker.cc
@@ -0,0 +1,262 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_test_packet_maker.h"
+
+#include <list>
+
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_http_utils.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using std::make_pair;
+
+namespace net {
+namespace test {
+
+QuicTestPacketMaker::QuicTestPacketMaker(QuicVersion version,
+                                         QuicConnectionId connection_id,
+                                         MockClock* clock)
+    : version_(version),
+      connection_id_(connection_id),
+      clock_(clock),
+      spdy_request_framer_(SPDY3),
+      spdy_response_framer_(SPDY3) {
+}
+
+QuicTestPacketMaker::~QuicTestPacketMaker() {
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeRstPacket(
+    QuicPacketSequenceNumber num,
+    bool include_version,
+    QuicStreamId stream_id,
+    QuicRstStreamErrorCode error_code) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = include_version;
+  header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = num;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.fec_group = 0;
+
+  QuicRstStreamFrame rst(stream_id, error_code, 0);
+  return scoped_ptr<QuicEncryptedPacket>(MakePacket(header, QuicFrame(&rst)));
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckAndRstPacket(
+    QuicPacketSequenceNumber num,
+    bool include_version,
+    QuicStreamId stream_id,
+    QuicRstStreamErrorCode error_code,
+    QuicPacketSequenceNumber largest_received,
+    QuicPacketSequenceNumber least_unacked,
+    bool send_feedback) {
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = include_version;
+  header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = num;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.fec_group = 0;
+
+  QuicAckFrame ack(MakeAckFrame(largest_received));
+  ack.delta_time_largest_observed = QuicTime::Delta::Zero();
+  if (version_ > QUIC_VERSION_22) {
+    for (QuicPacketSequenceNumber i = least_unacked; i <= largest_received;
+         ++i) {
+      ack.received_packet_times.push_back(make_pair(i, clock_->Now()));
+    }
+  }
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack));
+  QuicCongestionFeedbackFrame feedback;
+  if (send_feedback && version_ <= QUIC_VERSION_22) {
+    feedback.type = kTCP;
+    feedback.tcp.receive_window = 256000;
+
+    frames.push_back(QuicFrame(&feedback));
+  }
+
+  QuicStopWaitingFrame stop_waiting;
+  stop_waiting.least_unacked = least_unacked;
+  frames.push_back(QuicFrame(&stop_waiting));
+
+  QuicRstStreamFrame rst(stream_id, error_code, 0);
+  frames.push_back(QuicFrame(&rst));
+
+  QuicFramer framer(SupportedVersions(version_), clock_->Now(), false);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer, header, frames).packet);
+  return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+      ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeConnectionClosePacket(
+    QuicPacketSequenceNumber num) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = num;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.fec_group = 0;
+
+  QuicConnectionCloseFrame close;
+  close.error_code = QUIC_CRYPTO_VERSION_NOT_SUPPORTED;
+  close.error_details = "Time to panic!";
+  return scoped_ptr<QuicEncryptedPacket>(MakePacket(header, QuicFrame(&close)));
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeAckPacket(
+    QuicPacketSequenceNumber sequence_number,
+    QuicPacketSequenceNumber largest_received,
+    QuicPacketSequenceNumber least_unacked,
+    bool send_feedback) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id_;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = false;
+  header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = sequence_number;
+  header.entropy_flag = false;
+  header.fec_flag = false;
+  header.fec_group = 0;
+
+  QuicAckFrame ack(MakeAckFrame(largest_received));
+  ack.delta_time_largest_observed = QuicTime::Delta::Zero();
+  if (version_ > QUIC_VERSION_22) {
+    for (QuicPacketSequenceNumber i = least_unacked; i <= largest_received;
+         ++i) {
+      ack.received_packet_times.push_back(make_pair(i, clock_->Now()));
+    }
+  }
+
+  QuicFramer framer(SupportedVersions(version_), clock_->Now(), false);
+  QuicFrames frames;
+  frames.push_back(QuicFrame(&ack));
+  QuicCongestionFeedbackFrame feedback;
+  if (send_feedback && version_ <= QUIC_VERSION_22) {
+    feedback.type = kTCP;
+    feedback.tcp.receive_window = 256000;
+    frames.push_back(QuicFrame(&feedback));
+  }
+
+  QuicStopWaitingFrame stop_waiting;
+  stop_waiting.least_unacked = least_unacked;
+  frames.push_back(QuicFrame(&stop_waiting));
+
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer, header, frames).packet);
+  return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+      ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+}
+
+// Returns a newly created packet to send kData on stream 1.
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeDataPacket(
+    QuicPacketSequenceNumber sequence_number,
+    QuicStreamId stream_id,
+    bool should_include_version,
+    bool fin,
+    QuicStreamOffset offset,
+    base::StringPiece data) {
+  InitializeHeader(sequence_number, should_include_version);
+  QuicStreamFrame frame(stream_id, fin, offset, MakeIOVector(data));
+  return MakePacket(header_, QuicFrame(&frame));
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeRequestHeadersPacket(
+    QuicPacketSequenceNumber sequence_number,
+    QuicStreamId stream_id,
+    bool should_include_version,
+    bool fin,
+    const SpdyHeaderBlock& headers) {
+  InitializeHeader(sequence_number, should_include_version);
+  SpdySynStreamIR syn_stream(stream_id);
+  syn_stream.set_name_value_block(headers);
+  syn_stream.set_fin(fin);
+  syn_stream.set_priority(0);
+  scoped_ptr<SpdySerializedFrame> spdy_frame(
+      spdy_request_framer_.SerializeSynStream(syn_stream));
+  QuicStreamFrame frame(kHeadersStreamId, false, 0,
+                        MakeIOVector(base::StringPiece(spdy_frame->data(),
+                                                       spdy_frame->size())));
+  return MakePacket(header_, QuicFrame(&frame));
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakeResponseHeadersPacket(
+    QuicPacketSequenceNumber sequence_number,
+    QuicStreamId stream_id,
+    bool should_include_version,
+    bool fin,
+    const SpdyHeaderBlock& headers) {
+  InitializeHeader(sequence_number, should_include_version);
+  SpdySynReplyIR syn_reply(stream_id);
+  syn_reply.set_name_value_block(headers);
+  syn_reply.set_fin(fin);
+  scoped_ptr<SpdySerializedFrame> spdy_frame(
+      spdy_response_framer_.SerializeSynReply(syn_reply));
+  QuicStreamFrame frame(kHeadersStreamId, false, 0,
+                        MakeIOVector(base::StringPiece(spdy_frame->data(),
+                                                       spdy_frame->size())));
+  return MakePacket(header_, QuicFrame(&frame));
+}
+
+SpdyHeaderBlock QuicTestPacketMaker::GetRequestHeaders(
+    const std::string& method,
+    const std::string& scheme,
+    const std::string& path) {
+  SpdyHeaderBlock headers;
+  headers[":method"] = method;
+  headers[":host"] = "www.google.com";
+  headers[":path"] = path;
+  headers[":scheme"] = scheme;
+  headers[":version"] = "HTTP/1.1";
+  return headers;
+}
+
+SpdyHeaderBlock QuicTestPacketMaker::GetResponseHeaders(
+    const std::string& status) {
+  SpdyHeaderBlock headers;
+  headers[":status"] = status;
+  headers[":version"] = "HTTP/1.1";
+  headers["content-type"] = "text/plain";
+  return headers;
+}
+
+scoped_ptr<QuicEncryptedPacket> QuicTestPacketMaker::MakePacket(
+    const QuicPacketHeader& header,
+    const QuicFrame& frame) {
+  QuicFramer framer(SupportedVersions(version_), QuicTime::Zero(), false);
+  QuicFrames frames;
+  frames.push_back(frame);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer, header, frames).packet);
+  return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+      ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+}
+
+void QuicTestPacketMaker::InitializeHeader(
+    QuicPacketSequenceNumber sequence_number,
+    bool should_include_version) {
+  header_.public_header.connection_id = connection_id_;
+  header_.public_header.reset_flag = false;
+  header_.public_header.version_flag = should_include_version;
+  header_.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+  header_.packet_sequence_number = sequence_number;
+  header_.fec_group = 0;
+  header_.entropy_flag = false;
+  header_.fec_flag = false;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_test_packet_maker.h b/net/quic/test_tools/quic_test_packet_maker.h
new file mode 100644
index 0000000..9120eeb
--- /dev/null
+++ b/net/quic/test_tools/quic_test_packet_maker.h
@@ -0,0 +1,95 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Provides a simple interface for QUIC tests to create a variety of packets.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_TEST_PACKET_MAKER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_TEST_PACKET_MAKER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/request_priority.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+namespace test {
+
+class QuicTestPacketMaker {
+ public:
+  QuicTestPacketMaker(QuicVersion version,
+                      QuicConnectionId connection_id,
+                      MockClock* clock);
+  ~QuicTestPacketMaker();
+
+  scoped_ptr<QuicEncryptedPacket> MakeRstPacket(
+      QuicPacketSequenceNumber num,
+      bool include_version,
+      QuicStreamId stream_id,
+      QuicRstStreamErrorCode error_code);
+  scoped_ptr<QuicEncryptedPacket> MakeAckAndRstPacket(
+      QuicPacketSequenceNumber num,
+      bool include_version,
+      QuicStreamId stream_id,
+      QuicRstStreamErrorCode error_code,
+      QuicPacketSequenceNumber largest_received,
+      QuicPacketSequenceNumber least_unacked,
+      bool send_feedback);
+  scoped_ptr<QuicEncryptedPacket> MakeConnectionClosePacket(
+      QuicPacketSequenceNumber num);
+  scoped_ptr<QuicEncryptedPacket> MakeAckPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicPacketSequenceNumber largest_received,
+      QuicPacketSequenceNumber least_unacked,
+      bool send_feedback);
+  scoped_ptr<QuicEncryptedPacket> MakeDataPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      QuicStreamOffset offset,
+      base::StringPiece data);
+  scoped_ptr<QuicEncryptedPacket> MakeRequestHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      const SpdyHeaderBlock& headers);
+  scoped_ptr<QuicEncryptedPacket> MakeResponseHeadersPacket(
+      QuicPacketSequenceNumber sequence_number,
+      QuicStreamId stream_id,
+      bool should_include_version,
+      bool fin,
+      const SpdyHeaderBlock& headers);
+
+  SpdyHeaderBlock GetRequestHeaders(const std::string& method,
+                                    const std::string& scheme,
+                                    const std::string& path);
+  SpdyHeaderBlock GetResponseHeaders(const std::string& status);
+
+ private:
+  scoped_ptr<QuicEncryptedPacket> MakePacket(
+      const QuicPacketHeader& header,
+      const QuicFrame& frame);
+
+  void InitializeHeader(QuicPacketSequenceNumber sequence_number,
+                        bool should_include_version);
+
+  QuicVersion version_;
+  QuicConnectionId connection_id_;
+  MockClock* clock_;  // Owned by QuicStreamFactory.
+  SpdyFramer spdy_request_framer_;
+  SpdyFramer spdy_response_framer_;
+  MockRandom random_generator_;
+  QuicPacketHeader header_;
+
+  DISALLOW_COPY_AND_ASSIGN(QuicTestPacketMaker);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_TEST_PACKET_MAKER_H_
diff --git a/net/quic/test_tools/quic_test_utils.cc b/net/quic/test_tools/quic_test_utils.cc
new file mode 100644
index 0000000..0cf13e1
--- /dev/null
+++ b/net/quic/test_tools/quic_test_utils.cc
@@ -0,0 +1,678 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+#include "base/sha1.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/spdy/spdy_frame_builder.h"
+
+using base::StringPiece;
+using std::max;
+using std::min;
+using std::string;
+using testing::AnyNumber;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+// No-op alarm implementation used by MockHelper.
+class TestAlarm : public QuicAlarm {
+ public:
+  explicit TestAlarm(QuicAlarm::Delegate* delegate)
+      : QuicAlarm(delegate) {
+  }
+
+  virtual void SetImpl() OVERRIDE {}
+  virtual void CancelImpl() OVERRIDE {}
+};
+
+}  // namespace
+
+QuicAckFrame MakeAckFrame(QuicPacketSequenceNumber largest_observed) {
+  QuicAckFrame ack;
+  ack.largest_observed = largest_observed;
+  ack.entropy_hash = 0;
+  return ack;
+}
+
+QuicAckFrame MakeAckFrameWithNackRanges(
+    size_t num_nack_ranges, QuicPacketSequenceNumber least_unacked) {
+  QuicAckFrame ack = MakeAckFrame(2 * num_nack_ranges + least_unacked);
+  // Add enough missing packets to get num_nack_ranges nack ranges.
+  for (QuicPacketSequenceNumber i = 1; i < 2 * num_nack_ranges; i += 2) {
+    ack.missing_packets.insert(least_unacked + i);
+  }
+  return ack;
+}
+
+SerializedPacket BuildUnsizedDataPacket(QuicFramer* framer,
+                                        const QuicPacketHeader& header,
+                                        const QuicFrames& frames) {
+  const size_t max_plaintext_size = framer->GetMaxPlaintextSize(kMaxPacketSize);
+  size_t packet_size = GetPacketHeaderSize(header);
+  for (size_t i = 0; i < frames.size(); ++i) {
+    DCHECK_LE(packet_size, max_plaintext_size);
+    bool first_frame = i == 0;
+    bool last_frame = i == frames.size() - 1;
+    const size_t frame_size = framer->GetSerializedFrameLength(
+        frames[i], max_plaintext_size - packet_size, first_frame, last_frame,
+        header.is_in_fec_group,
+        header.public_header.sequence_number_length);
+    DCHECK(frame_size);
+    packet_size += frame_size;
+  }
+  return framer->BuildDataPacket(header, frames, packet_size);
+}
+
+uint64 SimpleRandom::RandUint64() {
+  unsigned char hash[base::kSHA1Length];
+  base::SHA1HashBytes(reinterpret_cast<unsigned char*>(&seed_), sizeof(seed_),
+                      hash);
+  memcpy(&seed_, hash, sizeof(seed_));
+  return seed_;
+}
+
+MockFramerVisitor::MockFramerVisitor() {
+  // By default, we want to accept packets.
+  ON_CALL(*this, OnProtocolVersionMismatch(_))
+      .WillByDefault(testing::Return(false));
+
+  // By default, we want to accept packets.
+  ON_CALL(*this, OnUnauthenticatedHeader(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnUnauthenticatedPublicHeader(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnPacketHeader(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnStreamFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnAckFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnCongestionFeedbackFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnStopWaitingFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnPingFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnRstStreamFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnConnectionCloseFrame(_))
+      .WillByDefault(testing::Return(true));
+
+  ON_CALL(*this, OnGoAwayFrame(_))
+      .WillByDefault(testing::Return(true));
+}
+
+MockFramerVisitor::~MockFramerVisitor() {
+}
+
+bool NoOpFramerVisitor::OnProtocolVersionMismatch(QuicVersion version) {
+  return false;
+}
+
+bool NoOpFramerVisitor::OnUnauthenticatedPublicHeader(
+    const QuicPacketPublicHeader& header) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnUnauthenticatedHeader(
+    const QuicPacketHeader& header) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnPacketHeader(const QuicPacketHeader& header) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnStreamFrame(const QuicStreamFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnAckFrame(const QuicAckFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnCongestionFeedbackFrame(
+    const QuicCongestionFeedbackFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnStopWaitingFrame(
+    const QuicStopWaitingFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnPingFrame(const QuicPingFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnRstStreamFrame(
+    const QuicRstStreamFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnConnectionCloseFrame(
+    const QuicConnectionCloseFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnWindowUpdateFrame(
+    const QuicWindowUpdateFrame& frame) {
+  return true;
+}
+
+bool NoOpFramerVisitor::OnBlockedFrame(const QuicBlockedFrame& frame) {
+  return true;
+}
+
+MockConnectionVisitor::MockConnectionVisitor() {
+}
+
+MockConnectionVisitor::~MockConnectionVisitor() {
+}
+
+MockHelper::MockHelper() {
+}
+
+MockHelper::~MockHelper() {
+}
+
+const QuicClock* MockHelper::GetClock() const {
+  return &clock_;
+}
+
+QuicRandom* MockHelper::GetRandomGenerator() {
+  return &random_generator_;
+}
+
+QuicAlarm* MockHelper::CreateAlarm(QuicAlarm::Delegate* delegate) {
+  return new TestAlarm(delegate);
+}
+
+void MockHelper::AdvanceTime(QuicTime::Delta delta) {
+  clock_.AdvanceTime(delta);
+}
+
+namespace {
+class NiceMockPacketWriterFactory
+    : public QuicConnection::PacketWriterFactory {
+ public:
+  NiceMockPacketWriterFactory() {}
+  virtual ~NiceMockPacketWriterFactory() {}
+
+  virtual QuicPacketWriter* Create(
+      QuicConnection* /*connection*/) const OVERRIDE {
+    return new testing::NiceMock<MockPacketWriter>();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NiceMockPacketWriterFactory);
+};
+}  // namespace
+
+MockConnection::MockConnection(bool is_server)
+    : QuicConnection(kTestConnectionId,
+                     IPEndPoint(TestPeerIPAddress(), kTestPort),
+                     new testing::NiceMock<MockHelper>(),
+                     NiceMockPacketWriterFactory(),
+                     /* owns_writer= */ true,
+                     is_server, QuicSupportedVersions()),
+      helper_(helper()) {
+}
+
+MockConnection::MockConnection(IPEndPoint address,
+                               bool is_server)
+    : QuicConnection(kTestConnectionId, address,
+                     new testing::NiceMock<MockHelper>(),
+                     NiceMockPacketWriterFactory(),
+                     /* owns_writer= */ true,
+                     is_server, QuicSupportedVersions()),
+      helper_(helper()) {
+}
+
+MockConnection::MockConnection(QuicConnectionId connection_id,
+                               bool is_server)
+    : QuicConnection(connection_id,
+                     IPEndPoint(TestPeerIPAddress(), kTestPort),
+                     new testing::NiceMock<MockHelper>(),
+                     NiceMockPacketWriterFactory(),
+                     /* owns_writer= */ true,
+                     is_server, QuicSupportedVersions()),
+      helper_(helper()) {
+}
+
+MockConnection::MockConnection(bool is_server,
+                               const QuicVersionVector& supported_versions)
+    : QuicConnection(kTestConnectionId,
+                     IPEndPoint(TestPeerIPAddress(), kTestPort),
+                     new testing::NiceMock<MockHelper>(),
+                     NiceMockPacketWriterFactory(),
+                     /* owns_writer= */ true,
+                     is_server, supported_versions),
+      helper_(helper()) {
+}
+
+MockConnection::~MockConnection() {
+}
+
+void MockConnection::AdvanceTime(QuicTime::Delta delta) {
+  static_cast<MockHelper*>(helper())->AdvanceTime(delta);
+}
+
+PacketSavingConnection::PacketSavingConnection(bool is_server)
+    : MockConnection(is_server) {
+}
+
+PacketSavingConnection::PacketSavingConnection(
+    bool is_server,
+    const QuicVersionVector& supported_versions)
+    : MockConnection(is_server, supported_versions) {
+}
+
+PacketSavingConnection::~PacketSavingConnection() {
+  STLDeleteElements(&packets_);
+  STLDeleteElements(&encrypted_packets_);
+}
+
+void PacketSavingConnection::SendOrQueuePacket(QueuedPacket packet) {
+  packets_.push_back(packet.serialized_packet.packet);
+  QuicEncryptedPacket* encrypted = QuicConnectionPeer::GetFramer(this)->
+      EncryptPacket(packet.encryption_level,
+                    packet.serialized_packet.sequence_number,
+                    *packet.serialized_packet.packet);
+  encrypted_packets_.push_back(encrypted);
+  // Transfer ownership of the packet to the SentPacketManager and the
+  // ack notifier to the AckNotifierManager.
+  sent_packet_manager_.OnPacketSent(
+      &packet.serialized_packet, 0, QuicTime::Zero(), 1000,
+      NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA);
+}
+
+MockSession::MockSession(QuicConnection* connection)
+    : QuicSession(connection, DefaultQuicConfig()) {
+  InitializeSession();
+  ON_CALL(*this, WritevData(_, _, _, _, _, _))
+      .WillByDefault(testing::Return(QuicConsumedData(0, false)));
+}
+
+MockSession::~MockSession() {
+}
+
+TestSession::TestSession(QuicConnection* connection, const QuicConfig& config)
+    : QuicSession(connection, config), crypto_stream_(nullptr) {
+  InitializeSession();
+}
+
+TestSession::~TestSession() {}
+
+void TestSession::SetCryptoStream(QuicCryptoStream* stream) {
+  crypto_stream_ = stream;
+}
+
+QuicCryptoStream* TestSession::GetCryptoStream() {
+  return crypto_stream_;
+}
+
+TestClientSession::TestClientSession(QuicConnection* connection,
+                                     const QuicConfig& config)
+    : QuicClientSessionBase(connection, config),
+      crypto_stream_(nullptr) {
+  EXPECT_CALL(*this, OnProofValid(_)).Times(AnyNumber());
+  InitializeSession();
+}
+
+TestClientSession::~TestClientSession() {}
+
+void TestClientSession::SetCryptoStream(QuicCryptoStream* stream) {
+  crypto_stream_ = stream;
+}
+
+QuicCryptoStream* TestClientSession::GetCryptoStream() {
+  return crypto_stream_;
+}
+
+MockPacketWriter::MockPacketWriter() {
+}
+
+MockPacketWriter::~MockPacketWriter() {
+}
+
+MockSendAlgorithm::MockSendAlgorithm() {
+}
+
+MockSendAlgorithm::~MockSendAlgorithm() {
+}
+
+MockLossAlgorithm::MockLossAlgorithm() {
+}
+
+MockLossAlgorithm::~MockLossAlgorithm() {
+}
+
+MockAckNotifierDelegate::MockAckNotifierDelegate() {
+}
+
+MockAckNotifierDelegate::~MockAckNotifierDelegate() {
+}
+
+MockNetworkChangeVisitor::MockNetworkChangeVisitor() {
+}
+
+MockNetworkChangeVisitor::~MockNetworkChangeVisitor() {
+}
+
+namespace {
+
+string HexDumpWithMarks(const char* data, int length,
+                        const bool* marks, int mark_length) {
+  static const char kHexChars[] = "0123456789abcdef";
+  static const int kColumns = 4;
+
+  const int kSizeLimit = 1024;
+  if (length > kSizeLimit || mark_length > kSizeLimit) {
+    LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+    length = min(length, kSizeLimit);
+    mark_length = min(mark_length, kSizeLimit);
+  }
+
+  string hex;
+  for (const char* row = data; length > 0;
+       row += kColumns, length -= kColumns) {
+    for (const char *p = row; p < row + 4; ++p) {
+      if (p < row + length) {
+        const bool mark =
+            (marks && (p - data) < mark_length && marks[p - data]);
+        hex += mark ? '*' : ' ';
+        hex += kHexChars[(*p & 0xf0) >> 4];
+        hex += kHexChars[*p & 0x0f];
+        hex += mark ? '*' : ' ';
+      } else {
+        hex += "    ";
+      }
+    }
+    hex = hex + "  ";
+
+    for (const char *p = row; p < row + 4 && p < row + length; ++p)
+      hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+
+    hex = hex + '\n';
+  }
+  return hex;
+}
+
+}  // namespace
+
+IPAddressNumber TestPeerIPAddress() { return Loopback4(); }
+
+QuicVersion QuicVersionMax() { return QuicSupportedVersions().front(); }
+
+QuicVersion QuicVersionMin() { return QuicSupportedVersions().back(); }
+
+IPAddressNumber Loopback4() {
+  IPAddressNumber addr;
+  CHECK(ParseIPLiteralToNumber("127.0.0.1", &addr));
+  return addr;
+}
+
+IPAddressNumber Loopback6() {
+  IPAddressNumber addr;
+  CHECK(ParseIPLiteralToNumber("::1", &addr));
+  return addr;
+}
+
+void GenerateBody(string* body, int length) {
+  body->clear();
+  body->reserve(length);
+  for (int i = 0; i < length; ++i) {
+    body->append(1, static_cast<char>(32 + i % (126 - 32)));
+  }
+}
+
+QuicEncryptedPacket* ConstructEncryptedPacket(
+    QuicConnectionId connection_id,
+    bool version_flag,
+    bool reset_flag,
+    QuicPacketSequenceNumber sequence_number,
+    const string& data) {
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id;
+  header.public_header.connection_id_length = PACKET_8BYTE_CONNECTION_ID;
+  header.public_header.version_flag = version_flag;
+  header.public_header.reset_flag = reset_flag;
+  header.public_header.sequence_number_length = PACKET_6BYTE_SEQUENCE_NUMBER;
+  header.packet_sequence_number = sequence_number;
+  header.entropy_flag = false;
+  header.entropy_hash = 0;
+  header.fec_flag = false;
+  header.is_in_fec_group = NOT_IN_FEC_GROUP;
+  header.fec_group = 0;
+  QuicStreamFrame stream_frame(1, false, 0, MakeIOVector(data));
+  QuicFrame frame(&stream_frame);
+  QuicFrames frames;
+  frames.push_back(frame);
+  QuicFramer framer(QuicSupportedVersions(), QuicTime::Zero(), false);
+  scoped_ptr<QuicPacket> packet(
+      BuildUnsizedDataPacket(&framer, header, frames).packet);
+  EXPECT_TRUE(packet != nullptr);
+  QuicEncryptedPacket* encrypted = framer.EncryptPacket(ENCRYPTION_NONE,
+                                                        sequence_number,
+                                                        *packet);
+  EXPECT_TRUE(encrypted != nullptr);
+  return encrypted;
+}
+
+void CompareCharArraysWithHexError(
+    const string& description,
+    const char* actual,
+    const int actual_len,
+    const char* expected,
+    const int expected_len) {
+  EXPECT_EQ(actual_len, expected_len);
+  const int min_len = min(actual_len, expected_len);
+  const int max_len = max(actual_len, expected_len);
+  scoped_ptr<bool[]> marks(new bool[max_len]);
+  bool identical = (actual_len == expected_len);
+  for (int i = 0; i < min_len; ++i) {
+    if (actual[i] != expected[i]) {
+      marks[i] = true;
+      identical = false;
+    } else {
+      marks[i] = false;
+    }
+  }
+  for (int i = min_len; i < max_len; ++i) {
+    marks[i] = true;
+  }
+  if (identical) return;
+  ADD_FAILURE()
+      << "Description:\n"
+      << description
+      << "\n\nExpected:\n"
+      << HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
+      << "\nActual:\n"
+      << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+bool DecodeHexString(const base::StringPiece& hex, std::string* bytes) {
+  bytes->clear();
+  if (hex.empty())
+    return true;
+  std::vector<uint8> v;
+  if (!base::HexStringToBytes(hex.as_string(), &v))
+    return false;
+  if (!v.empty())
+    bytes->assign(reinterpret_cast<const char*>(&v[0]), v.size());
+  return true;
+}
+
+static QuicPacket* ConstructPacketFromHandshakeMessage(
+    QuicConnectionId connection_id,
+    const CryptoHandshakeMessage& message,
+    bool should_include_version) {
+  CryptoFramer crypto_framer;
+  scoped_ptr<QuicData> data(crypto_framer.ConstructHandshakeMessage(message));
+  QuicFramer quic_framer(QuicSupportedVersions(), QuicTime::Zero(), false);
+
+  QuicPacketHeader header;
+  header.public_header.connection_id = connection_id;
+  header.public_header.reset_flag = false;
+  header.public_header.version_flag = should_include_version;
+  header.packet_sequence_number = 1;
+  header.entropy_flag = false;
+  header.entropy_hash = 0;
+  header.fec_flag = false;
+  header.fec_group = 0;
+
+  QuicStreamFrame stream_frame(kCryptoStreamId, false, 0,
+                               MakeIOVector(data->AsStringPiece()));
+
+  QuicFrame frame(&stream_frame);
+  QuicFrames frames;
+  frames.push_back(frame);
+  return BuildUnsizedDataPacket(&quic_framer, header, frames).packet;
+}
+
+QuicPacket* ConstructHandshakePacket(QuicConnectionId connection_id,
+                                     QuicTag tag) {
+  CryptoHandshakeMessage message;
+  message.set_tag(tag);
+  return ConstructPacketFromHandshakeMessage(connection_id, message, false);
+}
+
+size_t GetPacketLengthForOneStream(
+    QuicVersion version,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length,
+    InFecGroup is_in_fec_group,
+    size_t* payload_length) {
+  *payload_length = 1;
+  const size_t stream_length =
+      NullEncrypter().GetCiphertextSize(*payload_length) +
+      QuicPacketCreator::StreamFramePacketOverhead(
+          PACKET_8BYTE_CONNECTION_ID, include_version,
+          sequence_number_length, 0u, is_in_fec_group);
+  const size_t ack_length = NullEncrypter().GetCiphertextSize(
+      QuicFramer::GetMinAckFrameSize(
+          sequence_number_length, PACKET_1BYTE_SEQUENCE_NUMBER)) +
+      GetPacketHeaderSize(PACKET_8BYTE_CONNECTION_ID, include_version,
+                          sequence_number_length, is_in_fec_group);
+  if (stream_length < ack_length) {
+    *payload_length = 1 + ack_length - stream_length;
+  }
+
+  return NullEncrypter().GetCiphertextSize(*payload_length) +
+      QuicPacketCreator::StreamFramePacketOverhead(
+          PACKET_8BYTE_CONNECTION_ID, include_version,
+          sequence_number_length, 0u, is_in_fec_group);
+}
+
+TestEntropyCalculator::TestEntropyCalculator() {}
+
+TestEntropyCalculator::~TestEntropyCalculator() {}
+
+QuicPacketEntropyHash TestEntropyCalculator::EntropyHash(
+    QuicPacketSequenceNumber sequence_number) const {
+  return 1u;
+}
+
+MockEntropyCalculator::MockEntropyCalculator() {}
+
+MockEntropyCalculator::~MockEntropyCalculator() {}
+
+QuicConfig DefaultQuicConfig() {
+  QuicConfig config;
+  config.SetDefaults();
+  config.SetInitialFlowControlWindowToSend(
+      kInitialSessionFlowControlWindowForTest);
+  config.SetInitialStreamFlowControlWindowToSend(
+      kInitialStreamFlowControlWindowForTest);
+  config.SetInitialSessionFlowControlWindowToSend(
+      kInitialSessionFlowControlWindowForTest);
+  return config;
+}
+
+QuicVersionVector SupportedVersions(QuicVersion version) {
+  QuicVersionVector versions;
+  versions.push_back(version);
+  return versions;
+}
+
+TestWriterFactory::TestWriterFactory() : current_writer_(nullptr) {}
+TestWriterFactory::~TestWriterFactory() {}
+
+QuicPacketWriter* TestWriterFactory::Create(QuicServerPacketWriter* writer,
+                                            QuicConnection* connection) {
+  return new PerConnectionPacketWriter(this, writer, connection);
+}
+
+void TestWriterFactory::OnPacketSent(WriteResult result) {
+  if (current_writer_ != nullptr && result.status == WRITE_STATUS_ERROR) {
+    current_writer_->connection()->OnWriteError(result.error_code);
+    current_writer_ = nullptr;
+  }
+}
+
+void TestWriterFactory::Unregister(PerConnectionPacketWriter* writer) {
+  if (current_writer_ == writer) {
+    current_writer_ = nullptr;
+  }
+}
+
+TestWriterFactory::PerConnectionPacketWriter::PerConnectionPacketWriter(
+    TestWriterFactory* factory,
+    QuicServerPacketWriter* writer,
+    QuicConnection* connection)
+    : QuicPerConnectionPacketWriter(writer, connection),
+      factory_(factory) {
+}
+
+TestWriterFactory::PerConnectionPacketWriter::~PerConnectionPacketWriter() {
+  factory_->Unregister(this);
+}
+
+WriteResult TestWriterFactory::PerConnectionPacketWriter::WritePacket(
+    const char* buffer,
+    size_t buf_len,
+    const IPAddressNumber& self_address,
+    const IPEndPoint& peer_address) {
+  // A DCHECK(factory_current_writer_ == nullptr) would be wrong here -- this
+  // class may be used in a setting where connection()->OnPacketSent() is called
+  // in a different way, so TestWriterFactory::OnPacketSent might never be
+  // called.
+  factory_->current_writer_ = this;
+  return QuicPerConnectionPacketWriter::WritePacket(buffer,
+                                                    buf_len,
+                                                    self_address,
+                                                    peer_address);
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/quic_test_utils.h b/net/quic/test_tools/quic_test_utils.h
new file mode 100644
index 0000000..4c57a9b
--- /dev/null
+++ b/net/quic/test_tools/quic_test_utils.h
@@ -0,0 +1,591 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Common utilities for Quic tests
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/congestion_control/loss_detection_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_ack_notifier.h"
+#include "net/quic/quic_client_session_base.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_dispatcher.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_per_connection_packet_writer.h"
+#include "net/quic/quic_sent_packet_manager.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+
+namespace test {
+
+static const QuicConnectionId kTestConnectionId = 42;
+static const int kTestPort = 123;
+static const uint32 kInitialStreamFlowControlWindowForTest =
+    32 * 1024;  // 32 KB
+static const uint32 kInitialSessionFlowControlWindowForTest =
+    64 * 1024;  // 64 KB
+
+// Data stream IDs start at 5: the crypto stream is 1, headers stream is 3.
+static const QuicStreamId kClientDataStreamId1 = 5;
+static const QuicStreamId kClientDataStreamId2 = 7;
+static const QuicStreamId kClientDataStreamId3 = 9;
+static const QuicStreamId kClientDataStreamId4 = 11;
+
+// Returns the test peer IP address.
+IPAddressNumber TestPeerIPAddress();
+
+// Upper limit on versions we support.
+QuicVersion QuicVersionMax();
+
+// Lower limit on versions we support.
+QuicVersion QuicVersionMin();
+
+// Returns an address for 127.0.0.1.
+IPAddressNumber Loopback4();
+
+// Returns an address for ::1.
+IPAddressNumber Loopback6();
+
+void GenerateBody(std::string* body, int length);
+
+// Create an encrypted packet for testing.
+QuicEncryptedPacket* ConstructEncryptedPacket(
+    QuicConnectionId connection_id,
+    bool version_flag,
+    bool reset_flag,
+    QuicPacketSequenceNumber sequence_number,
+    const std::string& data);
+
+void CompareCharArraysWithHexError(const std::string& description,
+                                   const char* actual,
+                                   const int actual_len,
+                                   const char* expected,
+                                   const int expected_len);
+
+bool DecodeHexString(const base::StringPiece& hex, std::string* bytes);
+
+// Returns the length of a QuicPacket that is capable of holding either a
+// stream frame or a minimal ack frame.  Sets |*payload_length| to the number
+// of bytes of stream data that will fit in such a packet.
+size_t GetPacketLengthForOneStream(
+    QuicVersion version,
+    bool include_version,
+    QuicSequenceNumberLength sequence_number_length,
+    InFecGroup is_in_fec_group,
+    size_t* payload_length);
+
+// Returns QuicConfig set to default values.
+QuicConfig DefaultQuicConfig();
+
+// Returns a version vector consisting of |version|.
+QuicVersionVector SupportedVersions(QuicVersion version);
+
+// Testing convenience method to construct a QuicAckFrame with entropy_hash set
+// to 0 and largest_observed from peer set to |largest_observed|.
+QuicAckFrame MakeAckFrame(QuicPacketSequenceNumber largest_observed);
+
+// Testing convenience method to construct a QuicAckFrame with |num_nack_ranges|
+// nack ranges of width 1 packet, starting from |least_unacked|.
+QuicAckFrame MakeAckFrameWithNackRanges(size_t num_nack_ranges,
+                                        QuicPacketSequenceNumber least_unacked);
+
+// Returns a SerializedPacket whose |packet| member is owned by the caller, and
+// is populated with the fields in |header| and |frames|, or is nullptr if the
+// packet could not be created.
+SerializedPacket BuildUnsizedDataPacket(QuicFramer* framer,
+                                        const QuicPacketHeader& header,
+                                        const QuicFrames& frames);
+
+template<typename SaveType>
+class ValueRestore {
+ public:
+  ValueRestore(SaveType* name, SaveType value)
+      : name_(name),
+        value_(*name) {
+    *name_ = value;
+  }
+  ~ValueRestore() {
+    *name_ = value_;
+  }
+
+ private:
+  SaveType* name_;
+  SaveType value_;
+
+  DISALLOW_COPY_AND_ASSIGN(ValueRestore);
+};
+
+// Simple random number generator used to compute random numbers suitable
+// for pseudo-randomly dropping packets in tests.  It works by computing
+// the sha1 hash of the current seed, and using the first 64 bits as
+// the next random number, and the next seed.
+class SimpleRandom {
+ public:
+  SimpleRandom() : seed_(0) {}
+
+  // Returns a random number in the range [0, kuint64max].
+  uint64 RandUint64();
+
+  void set_seed(uint64 seed) { seed_ = seed; }
+
+ private:
+  uint64 seed_;
+
+  DISALLOW_COPY_AND_ASSIGN(SimpleRandom);
+};
+
+class MockFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+  MockFramerVisitor();
+  virtual ~MockFramerVisitor();
+
+  MOCK_METHOD1(OnError, void(QuicFramer* framer));
+  // The constructor sets this up to return false by default.
+  MOCK_METHOD1(OnProtocolVersionMismatch, bool(QuicVersion version));
+  MOCK_METHOD0(OnPacket, void());
+  MOCK_METHOD1(OnPublicResetPacket, void(const QuicPublicResetPacket& header));
+  MOCK_METHOD1(OnVersionNegotiationPacket,
+               void(const QuicVersionNegotiationPacket& packet));
+  MOCK_METHOD0(OnRevivedPacket, void());
+  // The constructor sets this up to return true by default.
+  MOCK_METHOD1(OnUnauthenticatedHeader, bool(const QuicPacketHeader& header));
+  // The constructor sets this up to return true by default.
+  MOCK_METHOD1(OnUnauthenticatedPublicHeader, bool(
+      const QuicPacketPublicHeader& header));
+  MOCK_METHOD1(OnDecryptedPacket, void(EncryptionLevel level));
+  MOCK_METHOD1(OnPacketHeader, bool(const QuicPacketHeader& header));
+  MOCK_METHOD1(OnFecProtectedPayload, void(base::StringPiece payload));
+  MOCK_METHOD1(OnStreamFrame, bool(const QuicStreamFrame& frame));
+  MOCK_METHOD1(OnAckFrame, bool(const QuicAckFrame& frame));
+  MOCK_METHOD1(OnCongestionFeedbackFrame,
+               bool(const QuicCongestionFeedbackFrame& frame));
+  MOCK_METHOD1(OnStopWaitingFrame, bool(const QuicStopWaitingFrame& frame));
+  MOCK_METHOD1(OnPingFrame, bool(const QuicPingFrame& frame));
+  MOCK_METHOD1(OnFecData, void(const QuicFecData& fec));
+  MOCK_METHOD1(OnRstStreamFrame, bool(const QuicRstStreamFrame& frame));
+  MOCK_METHOD1(OnConnectionCloseFrame,
+               bool(const QuicConnectionCloseFrame& frame));
+  MOCK_METHOD1(OnGoAwayFrame, bool(const QuicGoAwayFrame& frame));
+  MOCK_METHOD1(OnWindowUpdateFrame, bool(const QuicWindowUpdateFrame& frame));
+  MOCK_METHOD1(OnBlockedFrame, bool(const QuicBlockedFrame& frame));
+  MOCK_METHOD0(OnPacketComplete, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockFramerVisitor);
+};
+
+class NoOpFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+  NoOpFramerVisitor() {}
+
+  virtual void OnError(QuicFramer* framer) OVERRIDE {}
+  virtual void OnPacket() OVERRIDE {}
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) OVERRIDE {}
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) OVERRIDE {}
+  virtual void OnRevivedPacket() OVERRIDE {}
+  virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE;
+  virtual bool OnUnauthenticatedHeader(const QuicPacketHeader& header) OVERRIDE;
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) OVERRIDE;
+  virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE {}
+  virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+  virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE {}
+  virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+  virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+  virtual bool OnStopWaitingFrame(
+      const QuicStopWaitingFrame& frame) OVERRIDE;
+  virtual bool OnPingFrame(const QuicPingFrame& frame) OVERRIDE;
+  virtual void OnFecData(const QuicFecData& fec) OVERRIDE {}
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) OVERRIDE;
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+  virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& frame) OVERRIDE;
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE;
+  virtual void OnPacketComplete() OVERRIDE {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NoOpFramerVisitor);
+};
+
+class MockConnectionVisitor : public QuicConnectionVisitorInterface {
+ public:
+  MockConnectionVisitor();
+  virtual ~MockConnectionVisitor();
+
+  MOCK_METHOD1(OnStreamFrames, void(const std::vector<QuicStreamFrame>& frame));
+  MOCK_METHOD1(OnWindowUpdateFrames,
+               void(const std::vector<QuicWindowUpdateFrame>& frame));
+  MOCK_METHOD1(OnBlockedFrames,
+               void(const std::vector<QuicBlockedFrame>& frame));
+  MOCK_METHOD1(OnRstStream, void(const QuicRstStreamFrame& frame));
+  MOCK_METHOD1(OnGoAway, void(const QuicGoAwayFrame& frame));
+  MOCK_METHOD2(OnConnectionClosed, void(QuicErrorCode error, bool from_peer));
+  MOCK_METHOD0(OnWriteBlocked, void());
+  MOCK_METHOD0(OnCanWrite, void());
+  MOCK_METHOD1(OnCongestionWindowChange, void(QuicTime now));
+  MOCK_CONST_METHOD0(WillingAndAbleToWrite, bool());
+  MOCK_CONST_METHOD0(HasPendingHandshake, bool());
+  MOCK_CONST_METHOD0(HasOpenDataStreams, bool());
+  MOCK_METHOD1(OnSuccessfulVersionNegotiation,
+               void(const QuicVersion& version));
+  MOCK_METHOD0(OnConfigNegotiated, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockConnectionVisitor);
+};
+
+class MockHelper : public QuicConnectionHelperInterface {
+ public:
+  MockHelper();
+  virtual ~MockHelper();
+  virtual const QuicClock* GetClock() const OVERRIDE;
+  virtual QuicRandom* GetRandomGenerator() OVERRIDE;
+  virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE;
+  void AdvanceTime(QuicTime::Delta delta);
+
+ private:
+  MockClock clock_;
+  MockRandom random_generator_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHelper);
+};
+
+class MockConnection : public QuicConnection {
+ public:
+  // Uses a MockHelper, ConnectionId of 42, and 127.0.0.1:123.
+  explicit MockConnection(bool is_server);
+
+  // Uses a MockHelper, ConnectionId of 42.
+  MockConnection(IPEndPoint address, bool is_server);
+
+  // Uses a MockHelper, and 127.0.0.1:123
+  MockConnection(QuicConnectionId connection_id, bool is_server);
+
+  // Uses a Mock helper, ConnectionId of 42, and 127.0.0.1:123.
+  MockConnection(bool is_server, const QuicVersionVector& supported_versions);
+
+  virtual ~MockConnection();
+
+  // If the constructor that uses a MockHelper has been used then this method
+  // will advance the time of the MockClock.
+  void AdvanceTime(QuicTime::Delta delta);
+
+  MOCK_METHOD3(ProcessUdpPacket, void(const IPEndPoint& self_address,
+                                      const IPEndPoint& peer_address,
+                                      const QuicEncryptedPacket& packet));
+  MOCK_METHOD1(SendConnectionClose, void(QuicErrorCode error));
+  MOCK_METHOD2(SendConnectionCloseWithDetails, void(QuicErrorCode error,
+                                                    const string& details));
+  MOCK_METHOD2(SendConnectionClosePacket, void(QuicErrorCode error,
+                                               const string& details));
+  MOCK_METHOD3(SendRstStream, void(QuicStreamId id,
+                                   QuicRstStreamErrorCode error,
+                                   QuicStreamOffset bytes_written));
+  MOCK_METHOD3(SendGoAway, void(QuicErrorCode error,
+                                QuicStreamId last_good_stream_id,
+                                const string& reason));
+  MOCK_METHOD1(SendBlocked, void(QuicStreamId id));
+  MOCK_METHOD2(SendWindowUpdate, void(QuicStreamId id,
+                                      QuicStreamOffset byte_offset));
+  MOCK_METHOD0(OnCanWrite, void());
+
+  void ProcessUdpPacketInternal(const IPEndPoint& self_address,
+                                const IPEndPoint& peer_address,
+                                const QuicEncryptedPacket& packet) {
+    QuicConnection::ProcessUdpPacket(self_address, peer_address, packet);
+  }
+
+  virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+    return false;
+  }
+
+ private:
+  scoped_ptr<QuicConnectionHelperInterface> helper_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockConnection);
+};
+
+class PacketSavingConnection : public MockConnection {
+ public:
+  explicit PacketSavingConnection(bool is_server);
+
+  PacketSavingConnection(bool is_server,
+                         const QuicVersionVector& supported_versions);
+
+  virtual ~PacketSavingConnection();
+
+  virtual void SendOrQueuePacket(QueuedPacket packet) OVERRIDE;
+
+  std::vector<QuicPacket*> packets_;
+  std::vector<QuicEncryptedPacket*> encrypted_packets_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PacketSavingConnection);
+};
+
+class MockSession : public QuicSession {
+ public:
+  explicit MockSession(QuicConnection* connection);
+  virtual ~MockSession();
+  MOCK_METHOD2(OnConnectionClosed, void(QuicErrorCode error, bool from_peer));
+  MOCK_METHOD1(CreateIncomingDataStream, QuicDataStream*(QuicStreamId id));
+  MOCK_METHOD0(GetCryptoStream, QuicCryptoStream*());
+  MOCK_METHOD0(CreateOutgoingDataStream, QuicDataStream*());
+  MOCK_METHOD6(WritevData,
+               QuicConsumedData(QuicStreamId id,
+                                const IOVector& data,
+                                QuicStreamOffset offset,
+                                bool fin,
+                                FecProtection fec_protection,
+                                QuicAckNotifier::DelegateInterface*));
+  MOCK_METHOD2(OnStreamHeaders, void(QuicStreamId stream_id,
+                                     base::StringPiece headers_data));
+  MOCK_METHOD2(OnStreamHeadersPriority, void(QuicStreamId stream_id,
+                                             QuicPriority priority));
+  MOCK_METHOD3(OnStreamHeadersComplete, void(QuicStreamId stream_id,
+                                             bool fin,
+                                             size_t frame_len));
+  MOCK_METHOD3(SendRstStream, void(QuicStreamId stream_id,
+                                   QuicRstStreamErrorCode error,
+                                   QuicStreamOffset bytes_written));
+  MOCK_METHOD0(IsCryptoHandshakeConfirmed, bool());
+
+  using QuicSession::ActivateStream;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSession);
+};
+
+class TestSession : public QuicSession {
+ public:
+  TestSession(QuicConnection* connection, const QuicConfig& config);
+  virtual ~TestSession();
+
+  MOCK_METHOD1(CreateIncomingDataStream, QuicDataStream*(QuicStreamId id));
+  MOCK_METHOD0(CreateOutgoingDataStream, QuicDataStream*());
+
+  void SetCryptoStream(QuicCryptoStream* stream);
+
+  virtual QuicCryptoStream* GetCryptoStream() OVERRIDE;
+
+ private:
+  QuicCryptoStream* crypto_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestSession);
+};
+
+class TestClientSession : public QuicClientSessionBase {
+ public:
+  TestClientSession(QuicConnection* connection, const QuicConfig& config);
+  virtual ~TestClientSession();
+
+  // QuicClientSessionBase
+  MOCK_METHOD1(OnProofValid,
+               void(const QuicCryptoClientConfig::CachedState& cached));
+  MOCK_METHOD1(OnProofVerifyDetailsAvailable,
+               void(const ProofVerifyDetails& verify_details));
+
+  // TestClientSession
+  MOCK_METHOD1(CreateIncomingDataStream, QuicDataStream*(QuicStreamId id));
+  MOCK_METHOD0(CreateOutgoingDataStream, QuicDataStream*());
+
+  void SetCryptoStream(QuicCryptoStream* stream);
+
+  virtual QuicCryptoStream* GetCryptoStream() OVERRIDE;
+
+ private:
+  QuicCryptoStream* crypto_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestClientSession);
+};
+
+class MockPacketWriter : public QuicPacketWriter {
+ public:
+  MockPacketWriter();
+  virtual ~MockPacketWriter();
+
+  MOCK_METHOD4(WritePacket,
+               WriteResult(const char* buffer,
+                           size_t buf_len,
+                           const IPAddressNumber& self_address,
+                           const IPEndPoint& peer_address));
+  MOCK_CONST_METHOD0(IsWriteBlockedDataBuffered, bool());
+  MOCK_CONST_METHOD0(IsWriteBlocked, bool());
+  MOCK_METHOD0(SetWritable, void());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockPacketWriter);
+};
+
+class MockSendAlgorithm : public SendAlgorithmInterface {
+ public:
+  MockSendAlgorithm();
+  virtual ~MockSendAlgorithm();
+
+  MOCK_METHOD2(SetFromConfig, void(const QuicConfig& config, bool is_server));
+  MOCK_METHOD1(SetNumEmulatedConnections, void(int num_connections));
+  MOCK_METHOD1(SetMaxPacketSize, void(QuicByteCount max_packet_size));
+  MOCK_METHOD2(OnIncomingQuicCongestionFeedbackFrame,
+               void(const QuicCongestionFeedbackFrame&,
+                    QuicTime feedback_receive_time));
+  MOCK_METHOD4(OnCongestionEvent, void(bool rtt_updated,
+                                       QuicByteCount bytes_in_flight,
+                                       const CongestionVector& acked_packets,
+                                       const CongestionVector& lost_packets));
+  MOCK_METHOD5(OnPacketSent,
+               bool(QuicTime, QuicByteCount, QuicPacketSequenceNumber,
+                    QuicByteCount, HasRetransmittableData));
+  MOCK_METHOD1(OnRetransmissionTimeout, void(bool));
+  MOCK_METHOD0(RevertRetransmissionTimeout, void());
+  MOCK_CONST_METHOD3(TimeUntilSend,
+                     QuicTime::Delta(QuicTime now,
+                                     QuicByteCount bytes_in_flight,
+                                     HasRetransmittableData));
+  MOCK_CONST_METHOD0(BandwidthEstimate, QuicBandwidth(void));
+  MOCK_CONST_METHOD0(HasReliableBandwidthEstimate, bool());
+  MOCK_METHOD1(OnRttUpdated, void(QuicPacketSequenceNumber));
+  MOCK_CONST_METHOD0(RetransmissionDelay, QuicTime::Delta(void));
+  MOCK_CONST_METHOD0(GetCongestionWindow, QuicByteCount());
+  MOCK_CONST_METHOD0(InSlowStart, bool());
+  MOCK_CONST_METHOD0(InRecovery, bool());
+  MOCK_CONST_METHOD0(GetSlowStartThreshold, QuicByteCount());
+  MOCK_CONST_METHOD0(GetCongestionControlType, CongestionControlType());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSendAlgorithm);
+};
+
+class MockLossAlgorithm : public LossDetectionInterface {
+ public:
+  MockLossAlgorithm();
+  virtual ~MockLossAlgorithm();
+
+  MOCK_CONST_METHOD0(GetLossDetectionType, LossDetectionType());
+  MOCK_METHOD4(DetectLostPackets,
+               SequenceNumberSet(const QuicUnackedPacketMap& unacked_packets,
+                                 const QuicTime& time,
+                                 QuicPacketSequenceNumber largest_observed,
+                                 const RttStats& rtt_stats));
+  MOCK_CONST_METHOD0(GetLossTimeout, QuicTime());
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockLossAlgorithm);
+};
+
+class TestEntropyCalculator :
+      public QuicReceivedEntropyHashCalculatorInterface {
+ public:
+  TestEntropyCalculator();
+  virtual ~TestEntropyCalculator();
+
+  virtual QuicPacketEntropyHash EntropyHash(
+      QuicPacketSequenceNumber sequence_number) const OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestEntropyCalculator);
+};
+
+class MockEntropyCalculator : public TestEntropyCalculator {
+ public:
+  MockEntropyCalculator();
+  virtual ~MockEntropyCalculator();
+
+  MOCK_CONST_METHOD1(
+      EntropyHash,
+      QuicPacketEntropyHash(QuicPacketSequenceNumber sequence_number));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockEntropyCalculator);
+};
+
+class MockAckNotifierDelegate : public QuicAckNotifier::DelegateInterface {
+ public:
+  MockAckNotifierDelegate();
+
+  MOCK_METHOD5(OnAckNotification, void(int num_original_packets,
+                                       int num_original_bytes,
+                                       int num_retransmitted_packets,
+                                       int num_retransmitted_bytes,
+                                       QuicTime::Delta delta_largest_observed));
+
+ protected:
+  // Object is ref counted.
+  virtual ~MockAckNotifierDelegate();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockAckNotifierDelegate);
+};
+
+class MockNetworkChangeVisitor :
+      public QuicSentPacketManager::NetworkChangeVisitor {
+ public:
+  MockNetworkChangeVisitor();
+  virtual ~MockNetworkChangeVisitor();
+
+  MOCK_METHOD1(OnCongestionWindowChange, void(QuicByteCount));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockNetworkChangeVisitor);
+};
+
+// Creates per-connection packet writers that register themselves with the
+// TestWriterFactory on each write so that TestWriterFactory::OnPacketSent can
+// be routed to the appropriate QuicConnection.
+class TestWriterFactory : public QuicDispatcher::PacketWriterFactory {
+ public:
+  TestWriterFactory();
+  virtual ~TestWriterFactory();
+
+  virtual QuicPacketWriter* Create(QuicServerPacketWriter* writer,
+                                   QuicConnection* connection) OVERRIDE;
+
+  // Calls OnPacketSent on the last QuicConnection to write through one of the
+  // packet writers created by this factory.
+  void OnPacketSent(WriteResult result);
+
+ private:
+  class PerConnectionPacketWriter : public QuicPerConnectionPacketWriter {
+   public:
+    PerConnectionPacketWriter(TestWriterFactory* factory,
+                              QuicServerPacketWriter* writer,
+                              QuicConnection* connection);
+    virtual ~PerConnectionPacketWriter();
+
+    virtual WriteResult WritePacket(
+        const char* buffer,
+        size_t buf_len,
+        const IPAddressNumber& self_address,
+        const IPEndPoint& peer_address) OVERRIDE;
+
+   private:
+    TestWriterFactory* factory_;
+  };
+
+  // If an asynchronous write is happening and |writer| gets deleted, this
+  // clears the pointer to it to prevent use-after-free.
+  void Unregister(PerConnectionPacketWriter* writer);
+
+  PerConnectionPacketWriter* current_writer_;
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
diff --git a/net/quic/test_tools/reliable_quic_stream_peer.cc b/net/quic/test_tools/reliable_quic_stream_peer.cc
new file mode 100644
index 0000000..bd1ab00
--- /dev/null
+++ b/net/quic/test_tools/reliable_quic_stream_peer.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+
+#include <list>
+
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+namespace test {
+
+// static
+void ReliableQuicStreamPeer::SetWriteSideClosed(bool value,
+                                                ReliableQuicStream* stream) {
+  stream->write_side_closed_ = value;
+}
+
+// static
+void ReliableQuicStreamPeer::SetStreamBytesWritten(
+    QuicStreamOffset stream_bytes_written,
+    ReliableQuicStream* stream) {
+  stream->stream_bytes_written_ = stream_bytes_written;
+}
+
+// static
+void ReliableQuicStreamPeer::CloseReadSide(ReliableQuicStream* stream) {
+  stream->CloseReadSide();
+}
+
+// static
+bool ReliableQuicStreamPeer::FinSent(ReliableQuicStream* stream) {
+  return stream->fin_sent_;
+}
+
+// static
+bool ReliableQuicStreamPeer::RstSent(ReliableQuicStream* stream) {
+  return stream->rst_sent_;
+}
+
+// static
+uint32 ReliableQuicStreamPeer::SizeOfQueuedData(ReliableQuicStream* stream) {
+  uint32 total = 0;
+  std::list<ReliableQuicStream::PendingData>::iterator it =
+      stream->queued_data_.begin();
+  while (it != stream->queued_data_.end()) {
+    total += it->data.size();
+    ++it;
+  }
+  return total;
+}
+
+// static
+void ReliableQuicStreamPeer::SetFecPolicy(ReliableQuicStream* stream,
+                                          FecPolicy fec_policy) {
+  stream->set_fec_policy(fec_policy);
+}
+
+// static
+bool ReliableQuicStreamPeer::StreamContributesToConnectionFlowControl(
+    ReliableQuicStream* stream) {
+  return stream->stream_contributes_to_connection_flow_control_;
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/reliable_quic_stream_peer.h b/net/quic/test_tools/reliable_quic_stream_peer.h
new file mode 100644
index 0000000..8fffbbe
--- /dev/null
+++ b/net/quic/test_tools/reliable_quic_stream_peer.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
+#define NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ReliableQuicStream;
+
+namespace test {
+
+class ReliableQuicStreamPeer {
+ public:
+  static void SetWriteSideClosed(bool value, ReliableQuicStream* stream);
+  static void SetStreamBytesWritten(QuicStreamOffset stream_bytes_written,
+                                    ReliableQuicStream* stream);
+  static void CloseReadSide(ReliableQuicStream* stream);
+
+  static bool FinSent(ReliableQuicStream* stream);
+  static bool RstSent(ReliableQuicStream* stream);
+
+  static uint32 SizeOfQueuedData(ReliableQuicStream* stream);
+
+  static void SetFecPolicy(ReliableQuicStream* stream, FecPolicy fec_policy);
+
+  static bool StreamContributesToConnectionFlowControl(
+      ReliableQuicStream* stream);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReliableQuicStreamPeer);
+};
+
+}  // namespace test
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
diff --git a/net/quic/test_tools/simple_quic_framer.cc b/net/quic/test_tools/simple_quic_framer.cc
new file mode 100644
index 0000000..a08d555
--- /dev/null
+++ b/net/quic/test_tools/simple_quic_framer.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/simple_quic_framer.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class SimpleFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+  SimpleFramerVisitor()
+      : error_(QUIC_NO_ERROR) {
+  }
+
+  virtual ~SimpleFramerVisitor() OVERRIDE {
+    STLDeleteElements(&stream_data_);
+  }
+
+  virtual void OnError(QuicFramer* framer) OVERRIDE {
+    error_ = framer->error();
+  }
+
+  virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+    return false;
+  }
+
+  virtual void OnPacket() OVERRIDE {}
+  virtual void OnPublicResetPacket(
+      const QuicPublicResetPacket& packet) OVERRIDE {
+    public_reset_packet_.reset(new QuicPublicResetPacket(packet));
+  }
+  virtual void OnVersionNegotiationPacket(
+      const QuicVersionNegotiationPacket& packet) OVERRIDE {
+    version_negotiation_packet_.reset(
+        new QuicVersionNegotiationPacket(packet));
+  }
+  virtual void OnRevivedPacket() OVERRIDE {}
+
+  virtual bool OnUnauthenticatedPublicHeader(
+      const QuicPacketPublicHeader& header) OVERRIDE {
+    return true;
+  }
+  virtual bool OnUnauthenticatedHeader(
+      const QuicPacketHeader& header) OVERRIDE {
+    return true;
+  }
+  virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE {}
+  virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE {
+    has_header_ = true;
+    header_ = header;
+    return true;
+  }
+
+  virtual void OnFecProtectedPayload(StringPiece payload) OVERRIDE {}
+
+  virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+    // Save a copy of the data so it is valid after the packet is processed.
+    stream_data_.push_back(frame.GetDataAsString());
+    QuicStreamFrame stream_frame(frame);
+    // Make sure that the stream frame points to this data.
+    stream_frame.data.Clear();
+    stream_frame.data.Append(const_cast<char*>(stream_data_.back()->data()),
+                             stream_data_.back()->size());
+    stream_frames_.push_back(stream_frame);
+    return true;
+  }
+
+  virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE {
+    ack_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnCongestionFeedbackFrame(
+      const QuicCongestionFeedbackFrame& frame) OVERRIDE {
+    feedback_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnStopWaitingFrame(const QuicStopWaitingFrame& frame) OVERRIDE {
+    stop_waiting_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnPingFrame(const QuicPingFrame& frame) OVERRIDE {
+    ping_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual void OnFecData(const QuicFecData& fec) OVERRIDE {
+    fec_data_ = fec;
+    fec_redundancy_ = fec_data_.redundancy.as_string();
+    fec_data_.redundancy = fec_redundancy_;
+  }
+
+  virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE {
+    rst_stream_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnConnectionCloseFrame(
+      const QuicConnectionCloseFrame& frame) OVERRIDE {
+    connection_close_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE {
+    goaway_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnWindowUpdateFrame(
+      const QuicWindowUpdateFrame& frame) OVERRIDE {
+    window_update_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE {
+    blocked_frames_.push_back(frame);
+    return true;
+  }
+
+  virtual void OnPacketComplete() OVERRIDE {}
+
+  const QuicPacketHeader& header() const { return header_; }
+  const vector<QuicAckFrame>& ack_frames() const { return ack_frames_; }
+  const vector<QuicConnectionCloseFrame>& connection_close_frames() const {
+    return connection_close_frames_;
+  }
+  const vector<QuicCongestionFeedbackFrame>& feedback_frames() const {
+    return feedback_frames_;
+  }
+  const vector<QuicGoAwayFrame>& goaway_frames() const {
+    return goaway_frames_;
+  }
+  const vector<QuicRstStreamFrame>& rst_stream_frames() const {
+    return rst_stream_frames_;
+  }
+  const vector<QuicStreamFrame>& stream_frames() const {
+    return stream_frames_;
+  }
+  const vector<QuicStopWaitingFrame>& stop_waiting_frames() const {
+    return stop_waiting_frames_;
+  }
+  const vector<QuicPingFrame>& ping_frames() const {
+    return ping_frames_;
+  }
+  const QuicFecData& fec_data() const {
+    return fec_data_;
+  }
+  const QuicVersionNegotiationPacket* version_negotiation_packet() const {
+    return version_negotiation_packet_.get();
+  }
+  const QuicPublicResetPacket* public_reset_packet() const {
+    return public_reset_packet_.get();
+  }
+
+ private:
+  QuicErrorCode error_;
+  bool has_header_;
+  QuicPacketHeader header_;
+  QuicFecData fec_data_;
+  scoped_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+  scoped_ptr<QuicPublicResetPacket> public_reset_packet_;
+  string fec_redundancy_;
+  vector<QuicAckFrame> ack_frames_;
+  vector<QuicCongestionFeedbackFrame> feedback_frames_;
+  vector<QuicStopWaitingFrame> stop_waiting_frames_;
+  vector<QuicPingFrame> ping_frames_;
+  vector<QuicStreamFrame> stream_frames_;
+  vector<QuicRstStreamFrame> rst_stream_frames_;
+  vector<QuicGoAwayFrame> goaway_frames_;
+  vector<QuicConnectionCloseFrame> connection_close_frames_;
+  vector<QuicWindowUpdateFrame> window_update_frames_;
+  vector<QuicBlockedFrame> blocked_frames_;
+  vector<string*> stream_data_;
+
+  DISALLOW_COPY_AND_ASSIGN(SimpleFramerVisitor);
+};
+
+SimpleQuicFramer::SimpleQuicFramer()
+    : framer_(QuicSupportedVersions(), QuicTime::Zero(), true) {
+}
+
+SimpleQuicFramer::SimpleQuicFramer(const QuicVersionVector& supported_versions)
+    : framer_(supported_versions, QuicTime::Zero(), true) {
+}
+
+SimpleQuicFramer::~SimpleQuicFramer() {
+}
+
+bool SimpleQuicFramer::ProcessPacket(const QuicPacket& packet) {
+  scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+      ENCRYPTION_NONE, 0, packet));
+  return ProcessPacket(*encrypted);
+}
+
+bool SimpleQuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
+  visitor_.reset(new SimpleFramerVisitor);
+  framer_.set_visitor(visitor_.get());
+  return framer_.ProcessPacket(packet);
+}
+
+void SimpleQuicFramer::Reset() {
+  visitor_.reset(new SimpleFramerVisitor);
+}
+
+
+const QuicPacketHeader& SimpleQuicFramer::header() const {
+  return visitor_->header();
+}
+
+const QuicFecData& SimpleQuicFramer::fec_data() const {
+  return visitor_->fec_data();
+}
+
+const QuicVersionNegotiationPacket*
+SimpleQuicFramer::version_negotiation_packet() const {
+  return visitor_->version_negotiation_packet();
+}
+
+const QuicPublicResetPacket* SimpleQuicFramer::public_reset_packet() const {
+  return visitor_->public_reset_packet();
+}
+
+QuicFramer* SimpleQuicFramer::framer() {
+  return &framer_;
+}
+
+size_t SimpleQuicFramer::num_frames() const {
+  return ack_frames().size() +
+      feedback_frames().size() +
+      goaway_frames().size() +
+      rst_stream_frames().size() +
+      stop_waiting_frames().size() +
+      stream_frames().size() +
+      ping_frames().size() +
+      connection_close_frames().size();
+}
+
+const vector<QuicAckFrame>& SimpleQuicFramer::ack_frames() const {
+  return visitor_->ack_frames();
+}
+
+const vector<QuicStopWaitingFrame>&
+SimpleQuicFramer::stop_waiting_frames() const {
+  return visitor_->stop_waiting_frames();
+}
+
+const vector<QuicPingFrame>& SimpleQuicFramer::ping_frames() const {
+  return visitor_->ping_frames();
+}
+
+const vector<QuicStreamFrame>& SimpleQuicFramer::stream_frames() const {
+  return visitor_->stream_frames();
+}
+
+const vector<QuicRstStreamFrame>& SimpleQuicFramer::rst_stream_frames() const {
+  return visitor_->rst_stream_frames();
+}
+
+const vector<QuicCongestionFeedbackFrame>&
+SimpleQuicFramer::feedback_frames() const {
+  return visitor_->feedback_frames();
+}
+
+const vector<QuicGoAwayFrame>&
+SimpleQuicFramer::goaway_frames() const {
+  return visitor_->goaway_frames();
+}
+
+const vector<QuicConnectionCloseFrame>&
+SimpleQuicFramer::connection_close_frames() const {
+  return visitor_->connection_close_frames();
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/simple_quic_framer.h b/net/quic/test_tools/simple_quic_framer.h
new file mode 100644
index 0000000..7a31014
--- /dev/null
+++ b/net/quic/test_tools/simple_quic_framer.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
+#define NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+struct QuicAckFrame;
+class QuicConnection;
+class QuicConnectionVisitorInterface;
+class QuicPacketCreator;
+class ReceiveAlgorithmInterface;
+class SendAlgorithmInterface;
+
+namespace test {
+
+class SimpleFramerVisitor;
+
+// Peer to make public a number of otherwise private QuicFramer methods.
+class SimpleQuicFramer {
+ public:
+  SimpleQuicFramer();
+  explicit SimpleQuicFramer(const QuicVersionVector& supported_versions);
+  ~SimpleQuicFramer();
+
+  bool ProcessPacket(const QuicEncryptedPacket& packet);
+  bool ProcessPacket(const QuicPacket& packet);
+  void Reset();
+
+  const QuicPacketHeader& header() const;
+  size_t num_frames() const;
+  const std::vector<QuicAckFrame>& ack_frames() const;
+  const std::vector<QuicConnectionCloseFrame>& connection_close_frames() const;
+  const std::vector<QuicCongestionFeedbackFrame>& feedback_frames() const;
+  const std::vector<QuicStopWaitingFrame>& stop_waiting_frames() const;
+  const std::vector<QuicPingFrame>& ping_frames() const;
+  const std::vector<QuicGoAwayFrame>& goaway_frames() const;
+  const std::vector<QuicRstStreamFrame>& rst_stream_frames() const;
+  const std::vector<QuicStreamFrame>& stream_frames() const;
+  const QuicFecData& fec_data() const;
+  const QuicVersionNegotiationPacket* version_negotiation_packet() const;
+  const QuicPublicResetPacket* public_reset_packet() const;
+
+  QuicFramer* framer();
+
+  void SetSupportedVersions(const QuicVersionVector& versions) {
+    framer_.SetSupportedVersions(versions);
+  }
+
+ private:
+  QuicFramer framer_;
+  scoped_ptr<SimpleFramerVisitor> visitor_;
+  DISALLOW_COPY_AND_ASSIGN(SimpleQuicFramer);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
diff --git a/net/quic/test_tools/test_task_runner.cc b/net/quic/test_tools/test_task_runner.cc
new file mode 100644
index 0000000..385fd5d
--- /dev/null
+++ b/net/quic/test_tools/test_task_runner.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/quic/test_tools/test_task_runner.h"
+
+#include <algorithm>
+
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TestTaskRunner::TestTaskRunner(MockClock* clock)
+    : clock_(clock) {
+}
+
+TestTaskRunner::~TestTaskRunner() {
+}
+
+bool TestTaskRunner::PostDelayedTask(const tracked_objects::Location& from_here,
+                                     const base::Closure& task,
+                                     base::TimeDelta delay) {
+  EXPECT_GE(delay, base::TimeDelta());
+  tasks_.push_back(
+      PostedTask(from_here, task, clock_->NowInTicks(), delay,
+                 base::TestPendingTask::NESTABLE));
+  return false;
+}
+
+bool TestTaskRunner::RunsTasksOnCurrentThread() const {
+  return true;
+}
+
+const std::vector<PostedTask>& TestTaskRunner::GetPostedTasks() const {
+  return tasks_;
+}
+
+void TestTaskRunner::RunNextTask() {
+  // Find the next task to run, advance the time to the correct time
+  // and then run the task.
+  std::vector<PostedTask>::iterator next = FindNextTask();
+  DCHECK(next != tasks_.end());
+  clock_->AdvanceTime(QuicTime::Delta::FromMicroseconds(
+      (next->GetTimeToRun() - clock_->NowInTicks()).InMicroseconds()));
+  PostedTask task = *next;
+  tasks_.erase(next);
+  task.task.Run();
+}
+
+namespace {
+
+struct ShouldRunBeforeLessThan {
+  bool operator()(const PostedTask& task1, const PostedTask& task2) const {
+    return task1.ShouldRunBefore(task2);
+  }
+};
+
+}  // namespace
+
+std::vector<PostedTask>::iterator TestTaskRunner::FindNextTask() {
+  return std::min_element(
+      tasks_.begin(), tasks_.end(), ShouldRunBeforeLessThan());
+}
+
+}  // namespace test
+}  // namespace net
diff --git a/net/quic/test_tools/test_task_runner.h b/net/quic/test_tools/test_task_runner.h
new file mode 100644
index 0000000..e25bf2a
--- /dev/null
+++ b/net/quic/test_tools/test_task_runner.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Common utilities for Quic tests
+
+#ifndef NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_
+#define NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/task_runner.h"
+#include "base/test/test_pending_task.h"
+
+namespace net {
+
+class MockClock;
+
+namespace test {
+
+typedef base::TestPendingTask PostedTask;
+
+class TestTaskRunner : public base::TaskRunner {
+ public:
+  explicit TestTaskRunner(MockClock* clock);
+
+  // base::TaskRunner implementation.
+  virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+                               const base::Closure& task,
+                               base::TimeDelta delay) OVERRIDE;
+  virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
+  const std::vector<PostedTask>& GetPostedTasks() const;
+
+  void RunNextTask();
+
+ protected:
+  virtual ~TestTaskRunner();
+
+ private:
+  std::vector<PostedTask>::iterator FindNextTask();
+
+  MockClock* const clock_;
+  std::vector<PostedTask> tasks_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
+};
+
+}  // namespace test
+
+}  // namespace net
+
+#endif  // NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_