Advertise the permessage-deflate extension and enable it when requested by the server.

Also validation and parsing of the permessage-deflate extension parameters, and unit tests.

BUG=280910
TEST=net_unittests --gtest_filter=WebSocket*

Review URL: https://codereview.chromium.org/143913003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@247545 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc
index 3d2bcdf..808383bac 100644
--- a/net/websockets/websocket_basic_handshake_stream.cc
+++ b/net/websockets/websocket_basic_handshake_stream.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -14,6 +15,7 @@
 #include "base/bind.h"
 #include "base/containers/hash_tables.h"
 #include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
@@ -26,6 +28,10 @@
 #include "net/http/http_stream_parser.h"
 #include "net/socket/client_socket_handle.h"
 #include "net/websockets/websocket_basic_stream.h"
+#include "net/websockets/websocket_deflate_predictor.h"
+#include "net/websockets/websocket_deflate_predictor_impl.h"
+#include "net/websockets/websocket_deflate_stream.h"
+#include "net/websockets/websocket_deflater.h"
 #include "net/websockets/websocket_extension_parser.h"
 #include "net/websockets/websocket_handshake_constants.h"
 #include "net/websockets/websocket_handshake_handler.h"
@@ -34,6 +40,20 @@
 #include "net/websockets/websocket_stream.h"
 
 namespace net {
+
+// TODO(ricea): If more extensions are added, replace this with a more general
+// mechanism.
+struct WebSocketExtensionParams {
+  WebSocketExtensionParams()
+      : deflate_enabled(false),
+        client_window_bits(15),
+        deflate_mode(WebSocketDeflater::TAKE_OVER_CONTEXT) {}
+
+  bool deflate_enabled;
+  int client_window_bits;
+  WebSocketDeflater::ContextTakeOverMode deflate_mode;
+};
+
 namespace {
 
 enum GetHeaderResult {
@@ -202,12 +222,80 @@
   return true;
 }
 
+bool ValidatePerMessageDeflateExtension(const WebSocketExtension& extension,
+                                        std::string* failure_message,
+                                        WebSocketExtensionParams* params) {
+  static const char kClientPrefix[] = "client_";
+  static const char kServerPrefix[] = "server_";
+  static const char kNoContextTakeover[] = "no_context_takeover";
+  static const char kMaxWindowBits[] = "max_window_bits";
+  const size_t kPrefixLen = arraysize(kClientPrefix) - 1;
+  COMPILE_ASSERT(kPrefixLen == arraysize(kServerPrefix) - 1,
+                 the_strings_server_and_client_must_be_the_same_length);
+  typedef std::vector<WebSocketExtension::Parameter> ParameterVector;
+
+  DCHECK(extension.name() == "permessage-deflate");
+  const ParameterVector& parameters = extension.parameters();
+  std::set<std::string> seen_names;
+  for (ParameterVector::const_iterator it = parameters.begin();
+       it != parameters.end(); ++it) {
+    const std::string& name = it->name();
+    if (seen_names.count(name) != 0) {
+      *failure_message =
+          "Received duplicate permessage-deflate extension parameter " + name;
+      return false;
+    }
+    seen_names.insert(name);
+    const std::string client_or_server(name, 0, kPrefixLen);
+    const bool is_client = (client_or_server == kClientPrefix);
+    if (!is_client && client_or_server != kServerPrefix) {
+      *failure_message =
+          "Received an unexpected permessage-deflate extension parameter";
+      return false;
+    }
+    const std::string rest(name, kPrefixLen);
+    if (rest == kNoContextTakeover) {
+      if (it->HasValue()) {
+        *failure_message = "Received invalid " + name + " parameter";
+        return false;
+      }
+      if (is_client)
+        params->deflate_mode = WebSocketDeflater::DO_NOT_TAKE_OVER_CONTEXT;
+    } else if (rest == kMaxWindowBits) {
+      if (!it->HasValue()) {
+        *failure_message = name + " must have value";
+        return false;
+      }
+      int bits = 0;
+      if (!base::StringToInt(it->value(), &bits) || bits < 8 || bits > 15 ||
+          it->value()[0] == '0' ||
+          it->value().find_first_not_of("0123456789") != std::string::npos) {
+        *failure_message = "Received invalid " + name + " parameter";
+        return false;
+      }
+      if (is_client)
+        params->client_window_bits = bits;
+    } else {
+      *failure_message =
+          "Received an unexpected permessage-deflate extension parameter";
+      return false;
+    }
+  }
+  params->deflate_enabled = true;
+  return true;
+}
+
 bool ValidateExtensions(const HttpResponseHeaders* headers,
                         const std::vector<std::string>& requested_extensions,
                         std::string* extensions,
-                        std::string* failure_message) {
+                        std::string* failure_message,
+                        WebSocketExtensionParams* params) {
   void* state = NULL;
   std::string value;
+  std::vector<std::string> accepted_extensions;
+  // TODO(ricea): If adding support for additional extensions, generalise this
+  // code.
+  bool seen_permessage_deflate = false;
   while (headers->EnumerateHeader(
       &state, websockets::kSecWebSocketExtensions, &value)) {
     WebSocketExtensionParser parser;
@@ -220,13 +308,25 @@
           value;
       return false;
     }
-    // TODO(ricea): Accept permessage-deflate with valid parameters.
-    *failure_message =
-        "Found an unsupported extension '" +
-        parser.extension().name() +
-        "' in 'Sec-WebSocket-Extensions' header";
-    return false;
+    if (parser.extension().name() == "permessage-deflate") {
+      if (seen_permessage_deflate) {
+        *failure_message = "Received duplicate permessage-deflate response";
+        return false;
+      }
+      seen_permessage_deflate = true;
+      if (!ValidatePerMessageDeflateExtension(
+               parser.extension(), failure_message, params))
+        return false;
+    } else {
+      *failure_message =
+          "Found an unsupported extension '" +
+          parser.extension().name() +
+          "' in 'Sec-WebSocket-Extensions' header";
+      return false;
+    }
+    accepted_extensions.push_back(value);
   }
+  *extensions = JoinString(accepted_extensions, ", ");
   return true;
 }
 
@@ -284,12 +384,12 @@
   }
   enriched_headers.SetHeader(websockets::kSecWebSocketKey, handshake_challenge);
 
-  AddVectorHeaderIfNonEmpty(websockets::kSecWebSocketProtocol,
-                            requested_sub_protocols_,
-                            &enriched_headers);
   AddVectorHeaderIfNonEmpty(websockets::kSecWebSocketExtensions,
                             requested_extensions_,
                             &enriched_headers);
+  AddVectorHeaderIfNonEmpty(websockets::kSecWebSocketProtocol,
+                            requested_sub_protocols_,
+                            &enriched_headers);
 
   ComputeSecWebSocketAccept(handshake_challenge,
                             &handshake_challenge_response_);
@@ -393,16 +493,25 @@
 }
 
 scoped_ptr<WebSocketStream> WebSocketBasicHandshakeStream::Upgrade() {
-  // TODO(ricea): Add deflate support.
-
   // The HttpStreamParser object has a pointer to our ClientSocketHandle. Make
   // sure it does not touch it again before it is destroyed.
   state_.DeleteParser();
-  return scoped_ptr<WebSocketStream>(
+  scoped_ptr<WebSocketStream> basic_stream(
       new WebSocketBasicStream(state_.ReleaseConnection(),
                                state_.read_buf(),
                                sub_protocol_,
                                extensions_));
+  DCHECK(extension_params_.get());
+  if (extension_params_->deflate_enabled) {
+    return scoped_ptr<WebSocketStream>(
+        new WebSocketDeflateStream(basic_stream.Pass(),
+                                   extension_params_->deflate_mode,
+                                   extension_params_->client_window_bits,
+                                   scoped_ptr<WebSocketDeflatePredictor>(
+                                       new WebSocketDeflatePredictorImpl)));
+  } else {
+    return basic_stream.Pass();
+  }
 }
 
 void WebSocketBasicHandshakeStream::SetWebSocketKeyForTesting(
@@ -464,6 +573,7 @@
 
 int WebSocketBasicHandshakeStream::ValidateUpgradeResponse(
     const scoped_refptr<HttpResponseHeaders>& headers) {
+  extension_params_.reset(new WebSocketExtensionParams);
   if (ValidateUpgrade(headers.get(), &failure_message_) &&
       ValidateSecWebSocketAccept(headers.get(),
                                  handshake_challenge_response_,
@@ -476,7 +586,8 @@
       ValidateExtensions(headers.get(),
                          requested_extensions_,
                          &extensions_,
-                         &failure_message_)) {
+                         &failure_message_,
+                         extension_params_.get())) {
     return OK;
   }
   failure_message_ = "Error during WebSocket handshake: " + failure_message_;
diff --git a/net/websockets/websocket_basic_handshake_stream.h b/net/websockets/websocket_basic_handshake_stream.h
index 6ef57d4..71e835f 100644
--- a/net/websockets/websocket_basic_handshake_stream.h
+++ b/net/websockets/websocket_basic_handshake_stream.h
@@ -22,6 +22,8 @@
 class HttpResponseInfo;
 class HttpStreamParser;
 
+struct WebSocketExtensionParams;
+
 class NET_EXPORT_PRIVATE WebSocketBasicHandshakeStream
     : public WebSocketHandshakeStreamBase {
  public:
@@ -127,6 +129,10 @@
   // The extension(s) selected by the server.
   std::string extensions_;
 
+  // The extension parameters. The class is defined in the implementation file
+  // to avoid including extension-related header files here.
+  scoped_ptr<WebSocketExtensionParams> extension_params_;
+
   std::string failure_message_;
 
   DISALLOW_COPY_AND_ASSIGN(WebSocketBasicHandshakeStream);
diff --git a/net/websockets/websocket_handshake_stream_create_helper.cc b/net/websockets/websocket_handshake_stream_create_helper.cc
index 8268cda..b3ff3e2d 100644
--- a/net/websockets/websocket_handshake_stream_create_helper.cc
+++ b/net/websockets/websocket_handshake_stream_create_helper.cc
@@ -26,12 +26,17 @@
 WebSocketHandshakeStreamCreateHelper::CreateBasicStream(
     scoped_ptr<ClientSocketHandle> connection,
     bool using_proxy) {
+  // The list of supported extensions and parameters is hard-coded.
+  // TODO(ricea): If more extensions are added, consider a more flexible
+  // method.
+  std::vector<std::string> extensions(
+      1, "permessage-deflate; client_max_window_bits");
   return stream_ =
       new WebSocketBasicHandshakeStream(connection.Pass(),
                                         connect_delegate_,
                                         using_proxy,
                                         requested_subprotocols_,
-                                        std::vector<std::string>());
+                                        extensions);
 }
 
 // TODO(ricea): Create a WebSocketSpdyHandshakeStream. crbug.com/323852
diff --git a/net/websockets/websocket_handshake_stream_create_helper_test.cc b/net/websockets/websocket_handshake_stream_create_helper_test.cc
index 7cf7cdb..89e7cc3 100644
--- a/net/websockets/websocket_handshake_stream_create_helper_test.cc
+++ b/net/websockets/websocket_handshake_stream_create_helper_test.cc
@@ -4,6 +4,9 @@
 
 #include "net/websockets/websocket_handshake_stream_create_helper.h"
 
+#include <string>
+#include <vector>
+
 #include "net/base/completion_callback.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
@@ -146,14 +149,47 @@
   sub_protocols.push_back("chat");
   sub_protocols.push_back("superchat");
   scoped_ptr<WebSocketStream> stream =
-      CreateAndInitializeStream("ws://localhost/", "/",
-                                sub_protocols, "http://localhost/",
+      CreateAndInitializeStream("ws://localhost/",
+                                "/",
+                                sub_protocols,
+                                "http://localhost/",
                                 "Sec-WebSocket-Protocol: chat, superchat\r\n",
                                 "Sec-WebSocket-Protocol: superchat\r\n");
   EXPECT_EQ("superchat", stream->GetSubProtocol());
 }
 
-// TODO(ricea): Test extensions once they are implemented.
+// Verify that extension name is available. Bad extension names are tested in
+// websocket_stream_test.cc.
+TEST_F(WebSocketHandshakeStreamCreateHelperTest, Extensions) {
+  scoped_ptr<WebSocketStream> stream = CreateAndInitializeStream(
+      "ws://localhost/",
+      "/",
+      std::vector<std::string>(),
+      "http://localhost/",
+      "",
+      "Sec-WebSocket-Extensions: permessage-deflate\r\n");
+  EXPECT_EQ("permessage-deflate", stream->GetExtensions());
+}
+
+// Verify that extension parameters are available. Bad parameters are tested in
+// websocket_stream_test.cc.
+TEST_F(WebSocketHandshakeStreamCreateHelperTest, ExtensionParameters) {
+  scoped_ptr<WebSocketStream> stream = CreateAndInitializeStream(
+      "ws://localhost/",
+      "/",
+      std::vector<std::string>(),
+      "http://localhost/",
+      "",
+      "Sec-WebSocket-Extensions: permessage-deflate;"
+      " client_max_window_bits=14; server_max_window_bits=14;"
+      " server_no_context_takeover; client_no_context_takeover\r\n");
+
+  EXPECT_EQ(
+      "permessage-deflate;"
+      " client_max_window_bits=14; server_max_window_bits=14;"
+      " server_no_context_takeover; client_no_context_takeover",
+      stream->GetExtensions());
+}
 
 }  // namespace
 }  // namespace net
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc
index 300e763..6de9647 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -9,6 +9,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/memory/scoped_vector.h"
 #include "base/run_loop.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
@@ -17,6 +18,7 @@
 #include "net/socket/socket_test_util.h"
 #include "net/url_request/url_request_test_util.h"
 #include "net/websockets/websocket_basic_handshake_stream.h"
+#include "net/websockets/websocket_frame.h"
 #include "net/websockets/websocket_handshake_request_info.h"
 #include "net/websockets/websocket_handshake_response_info.h"
 #include "net/websockets/websocket_handshake_stream_create_helper.h"
@@ -184,6 +186,26 @@
   scoped_ptr<WebSocketHandshakeResponseInfo> response_info_;
 };
 
+// There are enough tests of the Sec-WebSocket-Extensions header that they
+// deserve their own test fixture.
+class WebSocketStreamCreateExtensionTest : public WebSocketStreamCreateTest {
+ public:
+  // Performs a standard connect, with the value of the Sec-WebSocket-Extensions
+  // header in the response set to |extensions_header_value|. Runs the event
+  // loop to allow the connect to complete.
+  void CreateAndConnectWithExtensions(
+      const std::string& extensions_header_value) {
+    CreateAndConnectStandard(
+        "ws://localhost/testing_path",
+        "/testing_path",
+        NoSubProtocols(),
+        "http://localhost/",
+        "",
+        "Sec-WebSocket-Extensions: " + extensions_header_value + "\r\n");
+    RunUntilIdle();
+  }
+};
+
 // Confirm that the basic case works as expected.
 TEST_F(WebSocketStreamCreateTest, SimpleSuccess) {
   CreateAndConnectStandard(
@@ -229,7 +251,7 @@
   EXPECT_EQ(GURL("ws://localhost/"), response_info_->url);
   EXPECT_EQ(101, response_info_->status_code);
   EXPECT_EQ("Switching Protocols", response_info_->status_text);
-  EXPECT_EQ(9u, request_headers.size());
+  EXPECT_EQ(10u, request_headers.size());
   EXPECT_EQ(HeaderKeyValuePair("Host", "localhost"), request_headers[0]);
   EXPECT_EQ(HeaderKeyValuePair("Connection", "Upgrade"), request_headers[1]);
   EXPECT_EQ(HeaderKeyValuePair("Upgrade", "websocket"), request_headers[2]);
@@ -243,6 +265,9 @@
   EXPECT_EQ(HeaderKeyValuePair("Accept-Language", "en-us,fr"),
             request_headers[7]);
   EXPECT_EQ("Sec-WebSocket-Key",  request_headers[8].first);
+  EXPECT_EQ(HeaderKeyValuePair("Sec-WebSocket-Extensions",
+                               "permessage-deflate; client_max_window_bits"),
+            request_headers[9]);
 
   std::vector<HeaderKeyValuePair> response_headers =
       ToVector(*response_info_->headers);
@@ -389,15 +414,52 @@
             failure_message());
 }
 
-// Unknown extension in the response is rejected
-TEST_F(WebSocketStreamCreateTest, UnknownExtension) {
-  CreateAndConnectStandard("ws://localhost/testing_path",
-                           "/testing_path",
-                           NoSubProtocols(),
-                           "http://localhost/",
-                           "",
-                           "Sec-WebSocket-Extensions: x-unknown-extension\r\n");
+// permessage-deflate extension basic success case.
+TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateSuccess) {
+  CreateAndConnectWithExtensions("permessage-deflate");
+  EXPECT_TRUE(stream_);
+  EXPECT_FALSE(has_failed());
+}
+
+// permessage-deflate extensions success with all parameters.
+TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateParamsSuccess) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_no_context_takeover; "
+      "server_max_window_bits=11; client_max_window_bits=13; "
+      "server_no_context_takeover");
+  EXPECT_TRUE(stream_);
+  EXPECT_FALSE(has_failed());
+}
+
+// Verify that incoming messages are actually decompressed with
+// permessage-deflate enabled.
+TEST_F(WebSocketStreamCreateExtensionTest, PerMessageDeflateInflates) {
+  CreateAndConnectCustomResponse(
+      "ws://localhost/testing_path",
+      "/testing_path",
+      NoSubProtocols(),
+      "http://localhost/",
+      "",
+      WebSocketStandardResponse(
+          "Sec-WebSocket-Extensions: permessage-deflate\r\n") +
+          std::string(
+              "\xc1\x07"  // WebSocket header (FIN + RSV1, Text payload 7 bytes)
+              "\xf2\x48\xcd\xc9\xc9\x07\x00",  // "Hello" DEFLATE compressed
+              9));
   RunUntilIdle();
+
+  ASSERT_TRUE(stream_);
+  ScopedVector<WebSocketFrame> frames;
+  CompletionCallback callback;
+  ASSERT_EQ(OK, stream_->ReadFrames(&frames, callback));
+  ASSERT_EQ(1U, frames.size());
+  ASSERT_EQ(5U, frames[0]->header.payload_length);
+  EXPECT_EQ("Hello", std::string(frames[0]->data->data(), 5));
+}
+
+// Unknown extension in the response is rejected
+TEST_F(WebSocketStreamCreateExtensionTest, UnknownExtension) {
+  CreateAndConnectWithExtensions("x-unknown-extension");
   EXPECT_FALSE(stream_);
   EXPECT_TRUE(has_failed());
   EXPECT_EQ("Error during WebSocket handshake: "
@@ -406,6 +468,155 @@
             failure_message());
 }
 
+// Malformed extensions are rejected (this file does not cover all possible
+// parse failures, as the parser is covered thoroughly by its own unit tests).
+TEST_F(WebSocketStreamCreateExtensionTest, MalformedExtension) {
+  CreateAndConnectWithExtensions(";");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: 'Sec-WebSocket-Extensions' header "
+      "value is rejected by the parser: ;",
+      failure_message());
+}
+
+// The permessage-deflate extension may only be specified once.
+TEST_F(WebSocketStreamCreateExtensionTest, OnlyOnePerMessageDeflateAllowed) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate, permessage-deflate; client_max_window_bits=10");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received duplicate permessage-deflate "
+      "response",
+      failure_message());
+}
+
+// permessage-deflate parameters may not be duplicated.
+TEST_F(WebSocketStreamCreateExtensionTest, NoDuplicateParameters) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_no_context_takeover; "
+      "client_no_context_takeover");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received duplicate permessage-deflate "
+      "extension parameter client_no_context_takeover",
+      failure_message());
+}
+
+// permessage-deflate parameters must start with "client_" or "server_"
+TEST_F(WebSocketStreamCreateExtensionTest, BadParameterPrefix) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; absurd_no_context_takeover");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received an unexpected "
+      "permessage-deflate extension parameter",
+      failure_message());
+}
+
+// permessage-deflate parameters must be either *_no_context_takeover or
+// *_max_window_bits
+TEST_F(WebSocketStreamCreateExtensionTest, BadParameterSuffix) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_max_content_bits=5");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received an unexpected "
+      "permessage-deflate extension parameter",
+      failure_message());
+}
+
+// *_no_context_takeover parameters must not have an argument
+TEST_F(WebSocketStreamCreateExtensionTest, BadParameterValue) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_no_context_takeover=true");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "client_no_context_takeover parameter",
+      failure_message());
+}
+
+// *_max_window_bits must have an argument
+TEST_F(WebSocketStreamCreateExtensionTest, NoMaxWindowBitsArgument) {
+  CreateAndConnectWithExtensions("permessage-deflate; client_max_window_bits");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: client_max_window_bits must have "
+      "value",
+      failure_message());
+}
+
+// *_max_window_bits must be an integer
+TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueInteger) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; server_max_window_bits=banana");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "server_max_window_bits parameter",
+      failure_message());
+}
+
+// *_max_window_bits must be >= 8
+TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooSmall) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; server_max_window_bits=7");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "server_max_window_bits parameter",
+      failure_message());
+}
+
+// *_max_window_bits must be <= 15
+TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueTooBig) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_max_window_bits=16");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "client_max_window_bits parameter",
+      failure_message());
+}
+
+// *_max_window_bits must not start with 0
+TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithZero) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; client_max_window_bits=08");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "client_max_window_bits parameter",
+      failure_message());
+}
+
+// *_max_window_bits must not start with +
+TEST_F(WebSocketStreamCreateExtensionTest, MaxWindowBitsValueStartsWithPlus) {
+  CreateAndConnectWithExtensions(
+      "permessage-deflate; server_max_window_bits=+9");
+  EXPECT_FALSE(stream_);
+  EXPECT_TRUE(has_failed());
+  EXPECT_EQ(
+      "Error during WebSocket handshake: Received invalid "
+      "server_max_window_bits parameter",
+      failure_message());
+}
+
+// TODO(ricea): Check that WebSocketDeflateStream is initialised with the
+// arguments from the server. This is difficult because the data written to the
+// socket is randomly masked.
+
 // Additional Sec-WebSocket-Accept headers should be rejected.
 TEST_F(WebSocketStreamCreateTest, DoubleAccept) {
   CreateAndConnectStandard(
diff --git a/net/websockets/websocket_test_util.cc b/net/websockets/websocket_test_util.cc
index 55113c6f..da030c5 100644
--- a/net/websockets/websocket_test_util.cc
+++ b/net/websockets/websocket_test_util.cc
@@ -44,6 +44,7 @@
       "Accept-Encoding: gzip,deflate\r\n"
       "Accept-Language: en-us,fr\r\n"
       "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+      "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
       "%s\r\n",
       path.c_str(),
       origin.c_str(),
@@ -87,8 +88,14 @@
   // We need to extend the lifetime of these strings.
   detail_->expect_written = expect_written;
   detail_->return_to_read = return_to_read;
-  detail_->write = MockWrite(SYNCHRONOUS, 0, detail_->expect_written.c_str());
-  detail_->read = MockRead(SYNCHRONOUS, 1, detail_->return_to_read.c_str());
+  detail_->write = MockWrite(SYNCHRONOUS,
+                             detail_->expect_written.data(),
+                             detail_->expect_written.size(),
+                             0);
+  detail_->read = MockRead(SYNCHRONOUS,
+                           detail_->return_to_read.data(),
+                           detail_->return_to_read.size(),
+                           1);
   scoped_ptr<DeterministicSocketData> socket_data(
       new DeterministicSocketData(&detail_->read, 1, &detail_->write, 1));
   socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));