Bug 1046892 part 2 - HTTP/2 draft 14 test updates r=mcmanus
authorNicholas Hurley <hurley@todesschaf.org>
Tue, 05 Aug 2014 08:41:11 -0700
changeset 197973 ac28ef70b9314f54d0b5aac364af2ed5d5e05df6
parent 197972 9f442730a6e74a32a40da1f5bf373cce081416a3
child 197974 72f85a52a2cafe08c74f35c5098104f84343c072
push id27256
push userkwierso@gmail.com
push dateWed, 06 Aug 2014 00:06:20 +0000
treeherdermozilla-central@6cbdd4d523a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs1046892
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1046892 part 2 - HTTP/2 draft 14 test updates r=mcmanus
netwerk/test/unit/test_http2.js
testing/xpcshell/node-http2/.travis.yml
testing/xpcshell/node-http2/HISTORY.md
testing/xpcshell/node-http2/README.md
testing/xpcshell/node-http2/lib/http.js
testing/xpcshell/node-http2/lib/index.js
testing/xpcshell/node-http2/node_modules/http2-protocol/HISTORY.md
testing/xpcshell/node-http2/node_modules/http2-protocol/README.md
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/compressor.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/connection.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/endpoint.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/flow.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/index.js
testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js
testing/xpcshell/node-http2/node_modules/http2-protocol/package.json
testing/xpcshell/node-http2/node_modules/http2-protocol/test/compressor.js
testing/xpcshell/node-http2/node_modules/http2-protocol/test/framer.js
testing/xpcshell/node-http2/package.json
testing/xpcshell/node-http2/test/http.js
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -20,17 +20,17 @@ posts.push(generateContent(250000));
 var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
             '2ef8d3b6c8f329318eb1a119b12622b6'];
 
 var bigListenerData = generateContent(128 * 1024);
 var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
 
 function checkIsHttp2(request) {
   try {
-    if (request.getResponseHeader("X-Firefox-Spdy") == "h2-13") {
+    if (request.getResponseHeader("X-Firefox-Spdy") == "h2-14") {
       if (request.getResponseHeader("X-Connection-Http2") == "yes") {
         return true;
       }
       return false; // Weird case, but the server disagrees with us
     }
   } catch (e) {
     // Nothing to do here
   }
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-http2/.travis.yml
@@ -0,0 +1,5 @@
+ language: node_js
+ node_js:
+   - "0.11"
+   - "0.10"
+
--- a/testing/xpcshell/node-http2/HISTORY.md
+++ b/testing/xpcshell/node-http2/HISTORY.md
@@ -1,11 +1,21 @@
 Version history
 ===============
 
+### 2.7.1 (2014-08-01) ###
+
+* Require protocol 0.14.1 (bugfix release)
+
+### 2.7.0 (2014-07-31) ###
+
+* Upgrade to the latest draft: [draft-ietf-httpbis-http2-14]
+
+[draft-ietf-httpbis-http2-14]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
+
 ### 2.6.0 (2014-06-18) ###
 
 * Upgrade to the latest draft: [draft-ietf-httpbis-http2-13]
 
 [draft-ietf-httpbis-http2-13]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
 
 ### 2.5.3 (2014-06-15) ###
 
--- a/testing/xpcshell/node-http2/README.md
+++ b/testing/xpcshell/node-http2/README.md
@@ -1,14 +1,16 @@
 node-http2
 ==========
 
-An HTTP/2 ([draft-ietf-httpbis-http2-13](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13))
+An HTTP/2 ([draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14))
 client and server implementation for node.js.
 
+![Travis CI status](https://travis-ci.org/molnarg/node-http2.svg?branch=master)
+
 Installation
 ------------
 
 ```
 npm install http2
 ```
 
 API
@@ -109,22 +111,22 @@ The developer documentation is generated
 ### Running the tests ###
 
 It's easy, just run `npm test`. The tests are written in BDD style, so they are a good starting
 point to understand the code.
 
 ### Test coverage ###
 
 To generate a code coverage report, run `npm test --coverage` (which runs very slowly, be patient).
-Code coverage summary as of version 2.5.3:
+Code coverage summary as of version 2.7.0:
 ```
-Statements   : 92.64% ( 403/435 )
-Branches     : 79.41% ( 135/170 )
+Statements   : 92.68% ( 405/437 )
+Branches     : 79.65% ( 137/172 )
 Functions    : 92.31% ( 60/65 )
-Lines        : 92.64% ( 403/435 )
+Lines        : 92.68% ( 405/437 )
 ```
 
 There's a hosted version of the detailed (line-by-line) coverage report
 [here](http://molnarg.github.io/node-http2/coverage/lcov-report/lib/).
 
 ### Logging ###
 
 Logging is turned off by default. You can turn it on by passing a bunyan logger as `log` option when
--- a/testing/xpcshell/node-http2/lib/http.js
+++ b/testing/xpcshell/node-http2/lib/http.js
@@ -116,17 +116,17 @@
 //   - **request.setSocketKeepAlive([enable], [initialDelay])**
 //
 // - **Class: http2.IncomingMessage**
 //   - **Event: 'close'**
 //   - **message.setTimeout(timeout, [callback])**
 //
 // [1]: http://nodejs.org/api/https.html
 // [2]: http://nodejs.org/api/http.html
-// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.2
+// [3]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2
 // [expect-continue]: https://github.com/http2/http2-spec/issues/18
 // [connect]: https://github.com/http2/http2-spec/issues/230
 
 // Common server and client side code
 // ==================================
 
 var net = require('net');
 var url = require('url');
@@ -241,17 +241,17 @@ function IncomingMessage(stream) {
   this._lastHeadersSeen = undefined;
 
   // * Other metadata is filled in when the headers arrive.
   stream.once('headers', this._onHeaders.bind(this));
   stream.once('end', this._onEnd.bind(this));
 }
 IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
 
-// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.1)
+// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
 //   of key-value pairs. This includes the target URI for the request, the status code for the
 //   response, as well as HTTP header fields.
 IncomingMessage.prototype._onHeaders = function _onHeaders(headers) {
   // * Detects malformed headers
   this._validateHeaders(headers);
 
   // * Store the _regular_ headers in `this.headers`
@@ -555,17 +555,17 @@ function createServer(options, requestLi
 // IncomingRequest class
 // ---------------------
 
 function IncomingRequest(stream) {
   IncomingMessage.call(this, stream);
 }
 IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } });
 
-// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.1)
+// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.1)
 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
 //   of key-value pairs. This includes the target URI for the request, the status code for the
 //   response, as well as HTTP header fields.
 IncomingRequest.prototype._onHeaders = function _onHeaders(headers) {
   // * The ":method" header field includes the HTTP method
   // * The ":scheme" header field includes the scheme portion of the target URI
   // * The ":authority" header field includes the authority portion of the target URI
   // * The ":path" header field includes the path and query parts of the target URI.
@@ -604,16 +604,20 @@ function OutgoingResponse(stream) {
   this.statusCode = 200;
   this.sendDate = true;
 
   this.stream.once('headers', this._onRequestHeaders.bind(this));
 }
 OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } });
 
 OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) {
+  if (this.headersSent) {
+    return;
+  }
+
   if (typeof reasonPhrase === 'string') {
     this._log.warn('Reason phrase argument was present but ignored by the writeHead method');
   } else {
     headers = reasonPhrase;
   }
 
   for (var name in headers) {
     this.setHeader(name, headers[name]);
@@ -994,17 +998,17 @@ OutgoingRequest.prototype._onPromise = f
 // IncomingResponse class
 // ----------------------
 
 function IncomingResponse(stream) {
   IncomingMessage.call(this, stream);
 }
 IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } });
 
-// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-8.1.3.2)
+// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.3.2)
 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series
 //   of key-value pairs. This includes the target URI for the request, the status code for the
 //   response, as well as HTTP header fields.
 IncomingResponse.prototype._onHeaders = function _onHeaders(headers) {
   // * A single ":status" header field is defined that carries the HTTP status code field. This
   //   header field MUST be included in all responses.
   // * A client MUST treat the absence of the ":status" header field, the presence of multiple
   //   values, or an invalid value as a stream error of type PROTOCOL_ERROR.
--- a/testing/xpcshell/node-http2/lib/index.js
+++ b/testing/xpcshell/node-http2/lib/index.js
@@ -1,22 +1,22 @@
-// [node-http2][homepage] is an [HTTP/2 (draft 13)][http2] implementation for [node.js][node].
+// [node-http2][homepage] is an [HTTP/2 (draft 14)][http2] implementation for [node.js][node].
 //
 // The core of the protocol is implemented by the [http2-protocol] module. This module provides
 // two important features on top of http2-protocol:
 //
 // * Implementation of different negotiation schemes that can be used to start a HTTP2 connection.
 //   These include TLS ALPN, Upgrade and Plain TCP.
 //
 // * Providing an API very similar to the standard node.js [HTTPS module API][node-https]
 //   (which is in turn very similar to the [HTTP module API][node-http]).
 //
 // [homepage]:            https://github.com/molnarg/node-http2
 // [http2-protocol]:      https://github.com/molnarg/node-http2-protocol
-// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
+// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
 // [node]:                http://nodejs.org/
 // [node-https]:          http://nodejs.org/api/https.html
 // [node-http]:           http://nodejs.org/api/http.html
 
 module.exports   = require('./http');
 
 /*
                   HTTP API
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/HISTORY.md
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/HISTORY.md
@@ -1,11 +1,21 @@
 Version history
 ===============
 
+### 0.14.1 (2014-08-01) ###
+
+* Fixed an error that caused us to send :-headers after non-:-headers
+
+### 0.14.0 (2014-07-31) ###
+
+* Upgrade to the latest draft: [draft-ietf-httpbis-http2-14][draft-14]
+
+[draft-14]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
+
 ### 0.13.0 (2014-06-18) ###
 
 * Upgrade to the latest draft: [draft-ietf-httpbis-http2-13][draft-13]
 
 [draft-13]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
 
 ### 0.12.3 (2014-06-15) ###
 
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/README.md
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/README.md
@@ -1,14 +1,16 @@
 node-http2-protocol
 ===================
 
-An HTTP/2 ([draft-ietf-httpbis-http2-13](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13))
+An HTTP/2 ([draft-ietf-httpbis-http2-14](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14))
 framing layer implementaion for node.js.
 
+![Travis CI status](https://travis-ci.org/molnarg/node-http2-protocol.svg?branch=master)
+
 Installation
 ------------
 
 ```
 npm install http2-protocol
 ```
 
 Examples
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/compressor.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/compressor.js
@@ -44,47 +44,18 @@ function HeaderTable(log, limit) {
   self._staticLength = self.length;
   self._size = 0;
   self._enforceLimit = HeaderTable.prototype._enforceLimit;
   self.add = HeaderTable.prototype.add;
   self.setSizeLimit = HeaderTable.prototype.setSizeLimit;
   return self;
 }
 
-// There are few more sets that are needed for the compression/decompression process that are all
-// subsets of the Header Table, and are implemented as flags on header table entries:
-//
-// * [Reference Set][referenceset]: contains a group of headers used as a reference for the
-//   differential encoding of a new set of headers. (`reference` flag)
-// * Emitted headers: the headers that are already emitted as part of the current decompression
-//   process (not part of the spec, `emitted` flag)
-// * Headers to be kept: headers that should not be removed as the last step of the encoding process
-//   (not part of the spec, `keep` flag)
-//
-// [referenceset]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.3
-//
-// Relations of the sets:
-//
-//     ,----------------------------------.
-//     |           Header Table           |
-//     |                                  |
-//     |  ,----------------------------.  |
-//     |  |        Reference Set       |  |
-//     |  |                            |  |
-//     |  |  ,---------.  ,---------.  |  |
-//     |  |  |  Keep   |  | Emitted |  |  |
-//     |  |  |         |  |         |  |  |
-//     |  |  `---------'  `---------'  |  |
-//     |  `----------------------------'  |
-//     `----------------------------------'
 function entryFromPair(pair) {
   var entry = pair.slice();
-  entry.reference = false;
-  entry.emitted = false;
-  entry.keep = false;
   entry._size = size(entry);
   return entry;
 }
 
 // The encoder decides how to update the header table and as such can control how much memory is
 // used by the header table.  To limit the memory requirements on the decoder side, the header table
 // size is bounded.
 //
@@ -103,42 +74,40 @@ function size(entry) {
 //
 // * it pushes the new `entry` at the beggining of the table
 // * before doing such a modification, it has to be ensured that the header table size will stay
 //   lower than or equal to the header table size limit. To achieve this, entries are evicted from
 //   the end of the header table until the size of the header table is less than or equal to
 //   `(this._limit - entry.size)`, or until the table is empty.
 //
 //              <----------  Index Address Space ---------->
-//              <-- Header  Table -->  <-- Static  Table -->
+//              <-- Static  Table -->  <-- Header  Table -->
 //              +---+-----------+---+  +---+-----------+---+
 //              | 0 |    ...    | k |  |k+1|    ...    | n |
 //              +---+-----------+---+  +---+-----------+---+
-//              ^                   |
-//              |                   V
-//       Insertion Point       Drop Point
+//                                     ^                   |
+//                                     |                   V
+//                              Insertion Point       Drop Point
 
 HeaderTable.prototype._enforceLimit = function _enforceLimit(limit) {
   var droppedEntries = [];
-  var dropPoint = this.length - this._staticLength;
-  while ((this._size > limit) && (dropPoint > 0)) {
-    dropPoint -= 1;
-    var dropped = this.splice(dropPoint, 1)[0];
+  while ((this._size > 0) && (this._size > limit)) {
+    var dropped = this.pop();
     this._size -= dropped._size;
-    droppedEntries[dropPoint] = dropped;
+    droppedEntries.unshift(dropped);
   }
   return droppedEntries;
 };
 
 HeaderTable.prototype.add = function(entry) {
   var limit = this._limit - entry._size;
   var droppedEntries = this._enforceLimit(limit);
 
   if (this._size <= limit) {
-    this.unshift(entry);
+    this.splice(this._staticLength, 0, entry);
     this._size += entry._size;
   }
 
   return droppedEntries;
 };
 
 // The table size limit can be changed externally. In this case, the same eviction algorithm is used
 HeaderTable.prototype.setSizeLimit = function setSizeLimit(limit) {
@@ -257,90 +226,56 @@ HeaderSetDecompressor.prototype._transfo
 //     }
 //
 // *Important:* to ease the indexing of the header table, indexes start at 0 instead of 1.
 //
 // Examples:
 //
 //     Indexed:
 //     { name: 2  , value: 2  , index: false }
-//     { name: -1 , value: -1 , index: false } // reference set emptying
 //     Literal:
 //     { name: 2  , value: 'X', index: false } // without indexing
 //     { name: 2  , value: 'Y', index: true  } // with indexing
 //     { name: 'A', value: 'Z', index: true  } // with indexing, literal name
 HeaderSetDecompressor.prototype._execute = function _execute(rep) {
   this._log.trace({ key: rep.name, value: rep.value, index: rep.index },
                   'Executing header representation');
 
   var entry, pair;
 
   if (rep.contextUpdate) {
-    if (rep.clearReferenceSet) {
-      for (var i = 0; i < this._table.length; i++) {
-        this._table[i].reference = false;
-      }
-    } else {
-      this.setTableSizeLimit(rep.newMaxSize);
-    }
+    this.setTableSizeLimit(rep.newMaxSize);
   }
 
-  // * An _indexed representation_ corresponding to an entry _present_ in the reference set
-  //   entails the following actions:
-  //   * The entry is removed from the reference set.
-  // * An _indexed representation_ corresponding to an entry _not present_ in the reference set
-  //   entails the following actions:
-  //   * If referencing an element of the static table:
-  //     * The header field corresponding to the referenced entry is emitted
-  //     * The referenced static entry is added to the header table
-  //     * A reference to this new header table entry is added to the reference set (except if
-  //       this new entry didn't fit in the header table)
-  //   * If referencing an element of the header table:
-  //     * The header field corresponding to the referenced entry is emitted
-  //     * The referenced header table entry is added to the reference set
+  // * An _indexed representation_ entails the following actions:
+  //   * The header field corresponding to the referenced entry is emitted
   else if (typeof rep.value === 'number') {
     var index = rep.value;
     entry = this._table[index];
 
-    if (entry.reference) {
-      entry.reference = false;
-    }
-
-    else {
-      pair = entry.slice();
-      this.push(pair);
-
-      if (index >= this._table.length - this._table._staticLength) {
-        entry = entryFromPair(pair);
-        this._table.add(entry);
-      }
-
-      entry.reference = true;
-      entry.emitted = true;
-    }
+    pair = entry.slice();
+    this.push(pair);
   }
 
   // * A _literal representation_ that is _not added_ to the header table entails the following
   //   action:
   //   * The header is emitted.
   // * A _literal representation_ that is _added_ to the header table entails the following further
   //   actions:
   //   * The header is added to the header table.
-  //   * The new entry is added to the reference set.
+  //   * The header is emitted.
   else {
     if (typeof rep.name === 'number') {
       pair = [this._table[rep.name][0], rep.value];
     } else {
       pair = [rep.name, rep.value];
     }
 
     if (rep.index) {
       entry = entryFromPair(pair);
-      entry.reference = true;
-      entry.emitted = true;
       this._table.add(entry);
     }
 
     this.push(pair);
   }
 };
 
 // `_flush` is the implementation of the [corresponding virtual function][_flush] of the
@@ -351,25 +286,16 @@ HeaderSetDecompressor.prototype._flush =
   var buffer = concat(this._chunks);
 
   // * processes the header representations
   buffer.cursor = 0;
   while (buffer.cursor < buffer.length) {
     this._execute(HeaderSetDecompressor.header(buffer));
   }
 
-  // * [emits the reference set](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.2.2)
-  for (var index = 0; index < this._table.length; index++) {
-    var entry = this._table[index];
-    if (entry.reference && !entry.emitted) {
-      this.push(entry.slice());
-    }
-    entry.emitted = false;
-  }
-
   callback();
 };
 
 // The HeaderSetCompressor class
 // -----------------------------
 
 // A `HeaderSetCompressor` instance is a transform stream that can be used to *compress a single
 // header set*. Its input is a stream of `[name, value]` pairs and its output is a stream of
@@ -416,116 +342,44 @@ HeaderSetCompressor.prototype._transform
         fullMatch = droppedIndex;
         break;
       } else if (nameMatch === -1) {
         nameMatch = droppedIndex;
       }
     }
   }
 
-  var mustNeverIndex = (name === 'cookie' || name === 'set-cookie' || name === 'authorization');
+  var mustNeverIndex = ((name === 'cookie' && value.length < 20) ||
+                        (name === 'set-cookie' && value.length < 20) ||
+                        name === 'authorization');
 
-  // * if there's full match, it will be an indexed representation (or more than one) depending
-  //   on its presence in the reference, the emitted and the keep set:
-  //
-  //   * If the entry is outside the reference set, then a single indexed representation puts the
-  //     entry into it and emits the header. Note that if the matched entry is in the static table,
-  //     then it has to be added to the header table.
-  //
-  //   * If it's already in the keep set, then 4 indexed representations are needed:
-  //
-  //     1. removes it from the reference set
-  //     2. puts it back in the reference set and emits the header once
-  //     3. removes it again
-  //     4. puts it back and emits it again for the second time
-  //
-  //     It won't be emitted at the end of the decoding process since it's now in the emitted set.
-  //
-  //   * If it's in the emitted set, then 2 indexed representations are needed:
-  //
-  //     1. removes it from the reference set
-  //     2. puts it back in the reference set and emits the header once
-  //
-  //   * If it's in the reference set, but outside the keep set and the emitted set, then this
-  //     header is common with the previous header set, and is still untouched. We mark it to keep
-  //     in the reference set (that means don't remove at the end of the encoding process).
   if (fullMatch !== -1 && !mustNeverIndex) {
-    rep = { name: fullMatch, value: fullMatch, index: false };
-
-    if (!entry.reference) {
-      if (fullMatch >= this._table.length - this._table._staticLength) {
-        entry = entryFromPair(pair);
-        this._table.add(entry);
-      }
-      this.send(rep);
-      entry.reference = true;
-      entry.emitted = true;
-    }
-
-    else if (entry.keep) {
-      this.send(rep);
-      this.send(rep);
-      this.send(rep);
-      this.send(rep);
-      entry.keep = false;
-      entry.emitted = true;
-    }
-
-    else if (entry.emitted) {
-      this.send(rep);
-      this.send(rep);
-    }
-
-    else {
-      entry.keep = true;
-    }
+    this.send({ name: fullMatch, value: fullMatch, index: false });
   }
 
   // * otherwise, it will be a literal representation (with a name index if there's a name match)
   else {
     entry = entryFromPair(pair);
-    entry.emitted = true;
 
     var indexing = (entry._size < this._table._limit / 2) && !mustNeverIndex;
 
     if (indexing) {
-      entry.reference = true;
-      var droppedEntries = this._table.add(entry);
-      for (droppedIndex in droppedEntries) {
-        droppedIndex = Number(droppedIndex)
-        var dropped = droppedEntries[droppedIndex];
-        if (dropped.keep) {
-          rep = { name: droppedIndex, value: droppedIndex, index: false };
-          this.send(rep);
-          this.send(rep);
-        }
-      }
+      this._table.add(entry);
     }
 
     this.send({ name: (nameMatch !== -1) ? nameMatch : name, value: value, index: indexing, mustNeverIndex: mustNeverIndex, contextUpdate: false });
   }
 
   callback();
 };
 
 // `_flush` is the implementation of the [corresponding virtual function][_flush] of the
 // TransformStream class. It gets called when there's no more header to compress. The final step:
 // [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback
 HeaderSetCompressor.prototype._flush = function _flush(callback) {
-  // * removing entries from the header set that are not marked to be kept or emitted
-  for (var index = 0; index < this._table.length; index++) {
-    var entry = this._table[index];
-    if (entry.reference && !entry.keep && !entry.emitted) {
-      this.send({ name: index, value: index, index: false });
-      entry.reference = false;
-    }
-    entry.keep = false;
-    entry.emitted = false;
-  }
-
   callback();
 };
 
 // [Detailed Format](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-4)
 // -----------------
 
 // ### Integer representation ###
 //
@@ -1130,22 +984,17 @@ HeaderSetCompressor.header = function wr
     representation = representations.literalIncremental;
   } else if (header.mustNeverIndex) {
     representation = representations.literalNeverIndexed;
   } else {
     representation = representations.literal;
   }
 
   if (representation === representations.contextUpdate) {
-    if (header.clearReferenceSet) {
-      var buffer = new Buffer('10', 'hex');
-      buffers.push([buffer]);
-    } else {
-      buffers.push(HeaderSetCompressor.integer(header.newMaxSize, 4));
-    }
+    buffers.push(HeaderSetCompressor.integer(header.newMaxSize, 5));
   }
 
   else if (representation === representations.indexed) {
     buffers.push(HeaderSetCompressor.integer(header.value + 1, representation.prefix));
   }
 
   else {
     if (typeof header.name === 'number') {
@@ -1176,28 +1025,22 @@ HeaderSetDecompressor.header = function 
     representation = representations.literalNeverIndexed;
   } else {
     representation = representations.literal;
   }
 
   header.value = header.name = -1;
   header.index = false;
   header.contextUpdate = false;
-  header.clearReferenceSet = false;
   header.newMaxSize = 0;
   header.mustNeverIndex = false;
 
   if (representation === representations.contextUpdate) {
     header.contextUpdate = true;
-    if (firstByte & 0x10) {
-      header.clearReferenceSet = true;
-      buffer.cursor += 1;
-    } else {
-      header.newMaxSize = HeaderSetDecompressor.integer(buffer, 4);
-    }
+    header.newMaxSize = HeaderSetDecompressor.integer(buffer, 5);
   }
 
   else if (representation === representations.indexed) {
     header.value = header.name = HeaderSetDecompressor.integer(buffer, representation.prefix) - 1;
   }
 
   else {
     header.name = HeaderSetDecompressor.integer(buffer, representation.prefix) - 1;
@@ -1229,17 +1072,17 @@ HeaderSetDecompressor.header = function 
 //       N1: 'V1',                         }
 //       N2: ['V1', 'V2', ...],
 //       // ...
 //      }
 //     }
 //
 // There are possibly several binary frame that belong to a single non-binary frame.
 
-var MAX_HTTP_PAYLOAD_SIZE = 16383;
+var MAX_HTTP_PAYLOAD_SIZE = 16384;
 
 // The Compressor class
 // --------------------
 
 // The Compressor transform stream is basically stateless.
 util.inherits(Compressor, TransformStream);
 function Compressor(log, type) {
   TransformStream.call(this, { objectMode: true });
@@ -1255,49 +1098,59 @@ Compressor.prototype.setTableSizeLimit =
   this._table.setSizeLimit(size);
 };
 
 // `compress` takes a header set, and compresses it using a new `HeaderSetCompressor` stream
 // instance. This means that from now on, the advantages of streaming header encoding are lost,
 // but the API becomes simpler.
 Compressor.prototype.compress = function compress(headers) {
   var compressor = new HeaderSetCompressor(this._log, this._table);
+  var colonHeaders = [];
+  var nonColonHeaders = [];
+
+  // To ensure we send colon headers first
   for (var name in headers) {
+    if (name.trim()[0] === ':') {
+      colonHeaders.push(name);
+    } else {
+      nonColonHeaders.push(name);
+    }
+  }
+
+  function compressHeader(name) {
     var value = headers[name];
     name = String(name).toLowerCase();
 
     // * To allow for better compression efficiency, the Cookie header field MAY be split into
     //   separate header fields, each with one or more cookie-pairs.
     if (name == 'cookie') {
       if (!(value instanceof Array)) {
         value = [value]
       }
       value = Array.prototype.concat.apply([], value.map(function(cookie) {
         return String(cookie).split(';').map(trim)
       }));
     }
 
-    // * To preserve the order of a comma-separated list, the ordered values for a single header
-    //   field name appearing in different header fields are concatenated into a single value.
-    //   A zero-valued octet (0x0) is used to delimit multiple values.
-    // * Header fields containing multiple values MUST be concatenated into a single value unless
-    //   the ordering of that header field is known to be not significant.
-    // * Currently, only the Cookie header is considered to be order-insensitive.
-    if ((value instanceof Array) && (name !== 'cookie')) {
-      value = value.join('\0');
-    }
-
     if (value instanceof Array) {
       for (var i = 0; i < value.length; i++) {
         compressor.write([name, String(value[i])]);
       }
     } else {
       compressor.write([name, String(value)]);
     }
   }
+
+  for (var idx = 0; idx < colonHeaders.length; idx++) {
+    compressHeader(colonHeaders[idx]);
+  }
+  for (var idx = 0; idx < nonColonHeaders.length; idx++) {
+    compressHeader(nonColonHeaders[idx]);
+  }
+
   compressor.end();
 
   var chunk, chunks = [];
   while (chunk = compressor.read()) {
     chunks.push(chunk);
   }
   return concat(chunks);
 };
@@ -1375,34 +1228,36 @@ Decompressor.prototype.setTableSizeLimit
 
 // `decompress` takes a full header block, and decompresses it using a new `HeaderSetDecompressor`
 // stream instance. This means that from now on, the advantages of streaming header decoding are
 // lost, but the API becomes simpler.
 Decompressor.prototype.decompress = function decompress(block) {
   var decompressor = new HeaderSetDecompressor(this._log, this._table);
   decompressor.end(block);
 
+  var seenNonColonHeader = false;
   var headers = {};
   var pair;
   while (pair = decompressor.read()) {
     var name = pair[0];
-    // * After decompression, header fields that have values containing zero octets (0x0) MUST be
-    //   split into multiple header fields before being processed.
-    var values = pair[1].split('\0');
-    for (var i = 0; i < values.length; i++) {
-      var value = values[i];
-      if (name in headers) {
-        if (headers[name] instanceof Array) {
-          headers[name].push(value);
-        } else {
-          headers[name] = [headers[name], value];
-        }
+    var value = pair[1];
+    var isColonHeader = (name.trim()[0] === ':');
+    if (seenNonColonHeader && isColonHeader) {
+        this.emit('error', 'PROTOCOL_ERROR');
+        return headers;
+    }
+    seenNonColonHeader = !isColonHeader;
+    if (name in headers) {
+      if (headers[name] instanceof Array) {
+        headers[name].push(value);
       } else {
-        headers[name] = value;
+        headers[name] = [headers[name], value];
       }
+    } else {
+      headers[name] = value;
     }
   }
 
   // * If there are multiple Cookie header fields after decompression, these MUST be concatenated
   //   into a single octet string using the two octet delimiter of 0x3B, 0x20 (the ASCII
   //   string "; ").
   if (('cookie' in headers) && (headers['cookie'] instanceof Array)) {
     headers['cookie'] = headers['cookie'].join('; ')
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/connection.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/connection.js
@@ -387,16 +387,17 @@ Connection.prototype._initializeSettings
 
   // * Sending the initial settings.
   this._log.debug({ settings: settings },
                   'Sending the first SETTINGS frame as part of the connection header.');
   this.set(settings || defaultSettings);
 
   // * Forwarding SETTINGS frames to the `_receiveSettings` method
   this.on('SETTINGS', this._receiveSettings);
+  this.on('RECEIVING_SETTINGS_MAX_FRAME_SIZE', this._sanityCheckMaxFrameSize);
 };
 
 // * Checking that the first frame the other endpoint sends is SETTINGS
 Connection.prototype._onFirstFrameReceived = function _onFirstFrameReceived(frame) {
   if ((frame.stream === 0) && (frame.type === 'SETTINGS')) {
     this._log.debug('Receiving the first SETTINGS frame as part of the connection header.');
   } else {
     this._log.fatal({ frame: frame }, 'Invalid connection header: first frame is not SETTINGS.');
@@ -425,16 +426,23 @@ Connection.prototype._receiveSettings = 
       });
     }
     for (var name in frame.settings) {
       this.emit('RECEIVING_' + name, frame.settings[name]);
     }
   }
 };
 
+Connection.prototype._sanityCheckMaxFrameSize = function _sanityCheckMaxFrameSize(value) {
+  if ((value < 0x4000) || (value >= 0x01000000)) {
+    this._log.fatal('Received invalid value for max frame size: ' + value);
+    this.emit('error');
+  }
+};
+
 // Changing one or more settings value and sending out a SETTINGS frame
 Connection.prototype.set = function set(settings, callback) {
   // * Calling the callback and emitting event when the change is acknowledges
   callback = callback || function noop() {};
   var self = this;
   this._settingsAckCallbacks.push(function() {
     for (var name in settings) {
       self.emit('ACKNOWLEDGED_' + name, settings[name]);
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/endpoint.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/endpoint.js
@@ -162,45 +162,43 @@ function createTransformStream(filter) {
 function pipeAndFilter(stream1, stream2, filter) {
   if (filter) {
     stream1.pipe(createTransformStream(filter)).pipe(stream2);
   } else {
     stream1.pipe(stream2);
   }
 }
 
-var MAX_HTTP_PAYLOAD_SIZE = 16383;
-
 Endpoint.prototype._initializeDataFlow = function _initializeDataFlow(role, settings, filters) {
   var firstStreamId, compressorRole, decompressorRole;
   if (role === 'CLIENT') {
     firstStreamId = 1;
     compressorRole = 'REQUEST';
     decompressorRole = 'RESPONSE';
   } else {
     firstStreamId = 2;
     compressorRole = 'RESPONSE';
     decompressorRole = 'REQUEST';
   }
 
-  this._serializer   = new Serializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
-  this._deserializer = new Deserializer(this._log, MAX_HTTP_PAYLOAD_SIZE);
+  this._serializer   = new Serializer(this._log);
+  this._deserializer = new Deserializer(this._log);
   this._compressor   = new Compressor(this._log, compressorRole);
   this._decompressor = new Decompressor(this._log, decompressorRole);
   this._connection   = new Connection(this._log, firstStreamId, settings);
 
   pipeAndFilter(this._connection, this._compressor, filters.beforeCompression);
   pipeAndFilter(this._compressor, this._serializer, filters.beforeSerialization);
   pipeAndFilter(this._deserializer, this._decompressor, filters.afterDeserialization);
   pipeAndFilter(this._decompressor, this._connection, filters.afterDecompression);
 
   this._connection.on('ACKNOWLEDGED_SETTINGS_HEADER_TABLE_SIZE',
-                      this._decompressor.setTableSizeLimit.bind(this._decompressor))
+                      this._decompressor.setTableSizeLimit.bind(this._decompressor));
   this._connection.on('RECEIVING_SETTINGS_HEADER_TABLE_SIZE',
-                      this._compressor.setTableSizeLimit.bind(this._compressor))
+                      this._compressor.setTableSizeLimit.bind(this._compressor));
 };
 
 var noread = {};
 Endpoint.prototype._read = function _read() {
   this._readableState.sync = true;
   var moreNeeded = noread, chunk;
   while (moreNeeded && (chunk = this._serializer.read())) {
     moreNeeded = this.push(chunk);
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/flow.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/flow.js
@@ -14,17 +14,17 @@ exports.Flow = Flow;
 // Public API
 // ----------
 
 // * **Event: 'error' (type)**: signals an error
 //
 // * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
 //   ([as described in the standard][1]) using this method
 //
-// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.9.2
+// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.9.2
 
 // API for child classes
 // ---------------------
 
 // * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
 //   with the given `flowControlId` (or every update frame if not given)
 //
 // * **_send()**: called when more frames should be pushed. The child class is expected to override
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/framer.js
@@ -6,50 +6,49 @@ var assert = require('assert');
 
 var Transform = require('stream').Transform;
 
 exports.Serializer = Serializer;
 exports.Deserializer = Deserializer;
 
 var logData = Boolean(process.env.HTTP2_LOG_DATA);
 
-var MAX_PAYLOAD_SIZE = 16383;
+var MAX_PAYLOAD_SIZE = 16384;
 var WINDOW_UPDATE_PAYLOAD_SIZE = 4;
 
 // Serializer
 // ----------
 //
 //     Frame Objects
 //     * * * * * * * --+---------------------------
 //                     |                          |
 //                     v                          v           Buffers
 //      [] -----> Payload Ser. --[buffers]--> Header Ser. --> * * * *
 //     empty      adds payload                adds header
 //     array        buffers                     buffer
 
-function Serializer(log, sizeLimit) {
+function Serializer(log) {
   this._log = log.child({ component: 'serializer' });
-  this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
   Transform.call(this, { objectMode: true });
 }
 Serializer.prototype = Object.create(Transform.prototype, { constructor: { value: Serializer } });
 
 // When there's an incoming frame object, it first generates the frame type specific part of the
 // frame (payload), and then then adds the header part which holds fields that are common to all
 // frame types (like the length of the payload).
 Serializer.prototype._transform = function _transform(frame, encoding, done) {
   this._log.trace({ frame: frame }, 'Outgoing frame');
 
   assert(frame.type in Serializer, 'Unknown frame type: ' + frame.type);
 
   var buffers = [];
   Serializer[frame.type](frame, buffers);
-  Serializer.commonHeader(frame, buffers);
+  var length = Serializer.commonHeader(frame, buffers);
 
-  assert(buffers[0].readUInt16BE(0) <= this._sizeLimit, 'Frame too large!');
+  assert(length <= MAX_PAYLOAD_SIZE, 'Frame too large!');
 
   for (var i = 0; i < buffers.length; i++) {
     if (logData) {
       this._log.trace({ data: buffers[i] }, 'Outgoing data');
     }
     this.push(buffers[i]);
   }
 
@@ -62,20 +61,19 @@ Serializer.prototype._transform = functi
 //     Buffers
 //     * * * * --------+-------------------------
 //                     |                        |
 //                     v                        v           Frame Objects
 //      {} -----> Header Des. --{frame}--> Payload Des. --> * * * * * * *
 //     empty      adds parsed              adds parsed
 //     object  header properties        payload properties
 
-function Deserializer(log, sizeLimit, role) {
+function Deserializer(log, role) {
   this._role = role;
   this._log = log.child({ component: 'deserializer' });
-  this._sizeLimit = sizeLimit || MAX_PAYLOAD_SIZE;
   Transform.call(this, { objectMode: true });
   this._next(COMMON_HEADER_SIZE);
 }
 Deserializer.prototype = Object.create(Transform.prototype, { constructor: { value: Deserializer } });
 
 // The Deserializer is stateful, and it's two main alternating states are: *waiting for header* and
 // *waiting for payload*. The state is stored in the boolean property `_waitingForHeader`.
 //
@@ -109,17 +107,17 @@ Deserializer.prototype._transform = func
 
     // When `_buffer` is full, it's content gets parsed either as header or payload depending on
     // the actual state.
 
     // If it's header then the parsed data is stored in a temporary variable and then the
     // deserializer waits for the specified length payload.
     if ((this._cursor === this._buffer.length) && this._waitingForHeader) {
       var payloadSize = Deserializer.commonHeader(this._buffer, this._frame);
-      if (payloadSize <= this._sizeLimit) {
+      if (payloadSize <= MAX_PAYLOAD_SIZE) {
         this._next(payloadSize);
       } else {
         this.emit('error', 'FRAME_SIZE_ERROR');
         return;
       }
     }
 
     // If it's payload then the the frame object is finalized and then gets pushed out.
@@ -143,43 +141,41 @@ Deserializer.prototype._transform = func
       }
       this._next(COMMON_HEADER_SIZE);
     }
   }
 
   done();
 };
 
-// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-4.1)
+// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-4.1)
 // --------------------------------------------------------------
 //
-// HTTP/2.0 frames share a common base format consisting of an 8-byte header followed by 0 to 65535
+// HTTP/2.0 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1
 // bytes of data.
 //
 // Additional size limits can be set by specific application uses. HTTP limits the frame size to
-// 16,383 octets.
+// 16,384 octets by default, though this can be increased by a receiver.
 //
 //      0                   1                   2                   3
 //      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//     | R |     Length (14)           |   Type (8)    |   Flags (8)   |
-//     +-+-+---------------------------+---------------+---------------+
+//     |                 Length (24)                   |
+//     +---------------+---------------+---------------+
+//     |   Type (8)    |   Flags (8)   |
+//     +-+-----------------------------+---------------+---------------+
 //     |R|                 Stream Identifier (31)                      |
 //     +-+-------------------------------------------------------------+
 //     |                     Frame Data (0...)                       ...
 //     +---------------------------------------------------------------+
 //
 // The fields of the frame header are defined as:
 //
-// * R:
-//   A reserved 2-bit field. The semantics of these bits are undefined and the bits MUST remain
-//   unset (0) when sending and MUST be ignored when receiving.
-//
 // * Length:
-//   The length of the frame data expressed as an unsigned 14-bit integer. The 8 bytes of the frame
+//   The length of the frame data expressed as an unsigned 24-bit integer. The 9 bytes of the frame
 //   header are not included in this value.
 //
 // * Type:
 //   The 8-bit type of the frame. The frame type determines how the remainder of the frame header
 //   and data are interpreted. Implementations MUST ignore unsupported and unrecognized frame types.
 //
 // * Flags:
 //   An 8-bit field reserved for frame-type specific boolean flags.
@@ -192,106 +188,109 @@ Deserializer.prototype._transform = func
 //   (0) when sending and MUST be ignored when receiving.
 //
 // * Stream Identifier:
 //   A 31-bit stream identifier. The value 0 is reserved for frames that are associated with the
 //   connection as a whole as opposed to an individual stream.
 //
 // The structure and content of the remaining frame data is dependent entirely on the frame type.
 
-var COMMON_HEADER_SIZE = 8;
+var COMMON_HEADER_SIZE = 9;
 
 var frameTypes = [];
 
 var frameFlags = {};
 
 var genericAttributes = ['type', 'flags', 'stream'];
 
 var typeSpecificAttributes = {};
 
 Serializer.commonHeader = function writeCommonHeader(frame, buffers) {
   var headerBuffer = new Buffer(COMMON_HEADER_SIZE);
 
   var size = 0;
   for (var i = 0; i < buffers.length; i++) {
     size += buffers[i].length;
   }
-  headerBuffer.writeUInt16BE(size, 0);
+  headerBuffer.writeUInt8(0, 0);
+  headerBuffer.writeUInt16BE(size, 1);
 
   var typeId = frameTypes.indexOf(frame.type);  // If we are here then the type is valid for sure
-  headerBuffer.writeUInt8(typeId, 2);
+  headerBuffer.writeUInt8(typeId, 3);
 
   var flagByte = 0;
   for (var flag in frame.flags) {
     var position = frameFlags[frame.type].indexOf(flag);
     assert(position !== -1, 'Unknown flag for frame type ' + frame.type + ': ' + flag);
     if (frame.flags[flag]) {
       flagByte |= (1 << position);
     }
   }
-  headerBuffer.writeUInt8(flagByte, 3);
+  headerBuffer.writeUInt8(flagByte, 4);
 
   assert((0 <= frame.stream) && (frame.stream < 0x7fffffff), frame.stream);
-  headerBuffer.writeUInt32BE(frame.stream || 0, 4);
+  headerBuffer.writeUInt32BE(frame.stream || 0, 5);
 
   buffers.unshift(headerBuffer);
+
+  return size;
 };
 
 Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
-  var length = buffer.readUInt16BE(0);
+  var totallyWastedByte = buffer.readUInt8(0);
+  var length = buffer.readUInt16BE(1);
+  // We do this just for sanity checking later on, to make sure no one sent us a
+  // frame that's super large.
+  length += totallyWastedByte << 16;
 
-  frame.type = frameTypes[buffer.readUInt8(2)];
+  frame.type = frameTypes[buffer.readUInt8(3)];
   if (!frame.type) {
     // We are required to ignore unknown frame types
     return length;
   }
 
   frame.flags = {};
-  var flagByte = buffer.readUInt8(3);
+  var flagByte = buffer.readUInt8(4);
   var definedFlags = frameFlags[frame.type];
   for (var i = 0; i < definedFlags.length; i++) {
     frame.flags[definedFlags[i]] = Boolean(flagByte & (1 << i));
   }
 
-  frame.stream = buffer.readUInt32BE(4) & 0x7fffffff;
+  frame.stream = buffer.readUInt32BE(5) & 0x7fffffff;
 
   return length;
 };
 
 // Frame types
 // ===========
 
 // Every frame type is registered in the following places:
 //
 // * `frameTypes`: a register of frame type codes (used by `commonHeader()`)
 // * `frameFlags`: a register of valid flags for frame types (used by `commonHeader()`)
 // * `typeSpecificAttributes`: a register of frame specific frame object attributes (used by
 //   logging code and also serves as documentation for frame objects)
 
-// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.1)
+// [DATA Frames](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.1)
 // ------------------------------------------------------------
 //
 // DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated with a
 // stream.
 //
 // The DATA frame defines the following flags:
 //
 // * END_STREAM (0x1):
 //   Bit 1 being set indicates that this frame is the last that the endpoint will send for the
 //   identified stream.
-// * END_SEGMENT (0x2):
-//   Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
-//   MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
-//   forwarding frames.
 // * PADDED (0x08):
 //   Bit 4 being set indicates that the Pad Length field is present.
 
 frameTypes[0x0] = 'DATA';
 
-frameFlags.DATA = ['END_STREAM', 'END_SEGMENT', 'RESERVED4', 'PADDED'];
+frameFlags.DATA = ['END_STREAM', 'RESERVED2', 'RESERVED4', 'PADDED'];
 
 typeSpecificAttributes.DATA = ['data'];
 
 Serializer.DATA = function writeData(frame, buffers) {
   buffers.push(frame.data);
 };
 
 Deserializer.DATA = function readData(buffer, frame) {
@@ -304,42 +303,38 @@ Deserializer.DATA = function readData(bu
 
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.2)
+// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.2)
 // --------------------------------------------------------------
 //
 // The HEADERS frame (type=0x1) allows the sender to create a stream.
 //
 // The HEADERS frame defines the following flags:
 //
 // * END_STREAM (0x1):
 //   Bit 1 being set indicates that this frame is the last that the endpoint will send for the
 //   identified stream.
-// * END_SEGMENT (0x2):
-//   Bit 2 being set indicates that this frame is the last for the current segment. Intermediaries
-//   MUST NOT coalesce frames across a segment boundary and MUST preserve segment boundaries when
-//   forwarding frames.
 // * END_HEADERS (0x4):
 //   The END_HEADERS bit indicates that this frame contains the entire payload necessary to provide
 //   a complete set of headers.
 // * PADDED (0x08):
 //   Bit 4 being set indicates that the Pad Length field is present.
 // * PRIORITY (0x20):
 //   Bit 6 being set indicates that the Exlusive Flag (E), Stream Dependency, and Weight fields are
 //   present.
 
 frameTypes[0x1] = 'HEADERS';
 
-frameFlags.HEADERS = ['END_STREAM', 'END_SEGMENT', 'END_HEADERS', 'PADDED', 'RESERVED5', 'PRIORITY'];
+frameFlags.HEADERS = ['END_STREAM', 'RESERVED2', 'END_HEADERS', 'PADDED', 'RESERVED5', 'PRIORITY'];
 
 typeSpecificAttributes.HEADERS = ['priorityDependency', 'priorityWeight', 'exclusiveDependency', 'headers', 'data'];
 
 //      0                   1                   2                   3
 //      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 //     |Pad Length? (8)|
 //     +-+-------------+---------------+-------------------------------+
@@ -390,17 +385,17 @@ Deserializer.HEADERS = function readHead
 
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.3)
+// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.3)
 // -------------------------------------------------------
 //
 // The PRIORITY frame (type=0x2) specifies the sender-advised priority of a stream.
 //
 // The PRIORITY frame does not define any flags.
 
 frameTypes[0x2] = 'PRIORITY';
 
@@ -435,17 +430,17 @@ Deserializer.PRIORITY = function readPri
   var dependencyData = new Buffer(4);
   buffer.copy(dependencyData, 0, 0, 4);
   frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
   dependencyData[0] &= 0x7f;
   frame.priorityDependency = dependencyData.readUInt32BE(0);
   frame.priorityWeight = buffer.readUInt8(4);
 };
 
-// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.4)
+// [RST_STREAM](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.4)
 // -----------------------------------------------------------
 //
 // The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream.
 //
 // No type-flags are defined.
 
 frameTypes[0x3] = 'RST_STREAM';
 
@@ -473,17 +468,17 @@ Serializer.RST_STREAM = function writeRs
 Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
   frame.error = errorCodes[buffer.readUInt32BE(0)];
   if (!frame.error) {
     // Unknown error codes are considered equivalent to INTERNAL_ERROR
     frame.error = 'INTERNAL_ERROR';
   }
 };
 
-// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.5)
+// [SETTINGS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.5)
 // -------------------------------------------------------
 //
 // The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
 // communicate.
 //
 // The SETTINGS frame defines the following flag:
 
 // * ACK (0x1):
@@ -576,17 +571,21 @@ definedSettings[2] = { name: 'SETTINGS_E
 // * SETTINGS_MAX_CONCURRENT_STREAMS (3):
 //   indicates the maximum number of concurrent streams that the sender will allow.
 definedSettings[3] = { name: 'SETTINGS_MAX_CONCURRENT_STREAMS', flag: false };
 
 // * SETTINGS_INITIAL_WINDOW_SIZE (4):
 //   indicates the sender's initial stream window size (in bytes) for new streams.
 definedSettings[4] = { name: 'SETTINGS_INITIAL_WINDOW_SIZE', flag: false };
 
-// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.6)
+// * SETTINGS_MAX_FRAME_SIZE (5):
+//   indicates the maximum size of a frame the receiver will allow.
+definedSettings[5] = { name: 'SETTINGS_MAX_FRAME_SIZE', flag: false };
+
+// [PUSH_PROMISE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.6)
 // ---------------------------------------------------------------
 //
 // The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of streams the
 // sender intends to initiate.
 //
 // The PUSH_PROMISE frame defines the following flags:
 //
 // * END_PUSH_PROMISE (0x4):
@@ -637,17 +636,17 @@ Deserializer.PUSH_PROMISE = function rea
   dataOffset += 4;
   if (paddingLength) {
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.7)
+// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.7)
 // -----------------------------------------------
 //
 // The PING frame (type=0x6) is a mechanism for measuring a minimal round-trip time from the
 // sender, as well as determining whether an idle connection is still functional.
 //
 // The PING frame defines one type-specific flag:
 //
 // * ACK (0x1):
@@ -667,17 +666,17 @@ Serializer.PING = function writePing(fra
 
 Deserializer.PING = function readPing(buffer, frame) {
   if (buffer.length !== 8) {
     return 'FRAME_SIZE_ERROR';
   }
   frame.data = buffer;
 };
 
-// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.8)
+// [GOAWAY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.8)
 // ---------------------------------------------------
 //
 // The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this connection.
 //
 // The GOAWAY frame does not define any flags.
 
 frameTypes[0x7] = 'GOAWAY';
 
@@ -718,17 +717,17 @@ Deserializer.GOAWAY = function readGoawa
   frame.last_stream = buffer.readUInt32BE(0) & 0x7fffffff;
   frame.error = errorCodes[buffer.readUInt32BE(4)];
   if (!frame.error) {
     // Unknown error types are to be considered equivalent to INTERNAL ERROR
     frame.error = 'INTERNAL_ERROR';
   }
 };
 
-// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.9)
+// [WINDOW_UPDATE](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.9)
 // -----------------------------------------------------------------
 //
 // The WINDOW_UPDATE frame (type=0x8) is used to implement flow control.
 //
 // The WINDOW_UPDATE frame does not define any flags.
 
 frameTypes[0x8] = 'WINDOW_UPDATE';
 
@@ -740,30 +739,33 @@ typeSpecificAttributes.WINDOW_UPDATE = [
 // that the sender can transmit in addition to the existing flow control window. The legal range
 // for this field is 1 to 2^31 - 1 (0x7fffffff) bytes; the most significant bit of this value is
 // reserved.
 
 Serializer.WINDOW_UPDATE = function writeWindowUpdate(frame, buffers) {
   var buffer = new Buffer(4);
 
   var window_size = frame.window_size;
-  assert((0 <= window_size) && (window_size <= 0x7fffffff), window_size);
+  assert((0 < window_size) && (window_size <= 0x7fffffff), window_size);
   buffer.writeUInt32BE(window_size, 0);
 
   buffers.push(buffer);
 };
 
 Deserializer.WINDOW_UPDATE = function readWindowUpdate(buffer, frame) {
   if (buffer.length !== WINDOW_UPDATE_PAYLOAD_SIZE) {
     return 'FRAME_SIZE_ERROR';
   }
   frame.window_size = buffer.readUInt32BE(0) & 0x7fffffff;
+  if (frame.window_size === 0) {
+    return 'PROTOCOL_ERROR';
+  }
 };
 
-// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.10)
+// [CONTINUATION](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.10)
 // ------------------------------------------------------------
 //
 // The CONTINUATION frame (type=0x9) is used to continue a sequence of header block fragments.
 //
 // The CONTINUATION frame defines the following flag:
 //
 // * END_HEADERS (0x4):
 //   The END_HEADERS bit indicates that this frame ends the sequence of header block fragments
@@ -778,17 +780,17 @@ typeSpecificAttributes.CONTINUATION = ['
 Serializer.CONTINUATION = function writeContinuation(frame, buffers) {
   buffers.push(frame.data);
 };
 
 Deserializer.CONTINUATION = function readContinuation(buffer, frame) {
   frame.data = buffer;
 };
 
-// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.11)
+// [ALTSVC](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.11)
 // ------------------------------------------------------------
 //
 // The ALTSVC frame (type=0xA) advertises the availability of an alternative service to the client.
 //
 // The ALTSVC frame does not define any flags.
 
 frameTypes[0xA] = 'ALTSVC';
 
@@ -867,17 +869,17 @@ Deserializer.ALTSVC = function readAltSv
   frame.port = buffer.readUInt16BE(4);
   var pidLength = buffer.readUInt8(7);
   frame.protocolID = buffer.toString('ascii', 8, 8 + pidLength);
   var hostLength = buffer.readUInt8(8 + pidLength);
   frame.host = buffer.toString('ascii', 9 + pidLength, 9 + pidLength + hostLength);
   frame.origin = buffer.toString('ascii', 9 + pidLength + hostLength);
 };
 
-// [BLOCKED](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-6.12)
+// [BLOCKED](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-6.12)
 // ------------------------------------------------------------
 //
 // The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
 // due to a closed flow control window.
 //
 // The BLOCKED frame does not define any flags and contains no payload.
 
 frameTypes[0xB] = 'BLOCKED';
@@ -887,17 +889,17 @@ frameFlags.BLOCKED = [];
 typeSpecificAttributes.BLOCKED = [];
 
 Serializer.BLOCKED = function writeBlocked(frame, buffers) {
 };
 
 Deserializer.BLOCKED = function readBlocked(buffer, frame) {
 };
 
-// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-7)
+// [Error Codes](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-7)
 // ------------------------------------------------------------
 
 var errorCodes = [
   'NO_ERROR',
   'PROTOCOL_ERROR',
   'INTERNAL_ERROR',
   'FLOW_CONTROL_ERROR',
   'SETTINGS_TIMEOUT',
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/index.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/index.js
@@ -1,9 +1,9 @@
-// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 13)][http2]
+// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 14)][http2]
 // framing layer for [node.js][node].
 //
 // The main building blocks are [node.js streams][node-stream] that are connected through pipes.
 //
 // The main components are:
 //
 // * [Endpoint](endpoint.html): represents an HTTP/2 endpoint (client or server). It's
 //   responsible for the the first part of the handshake process (sending/receiving the
@@ -23,26 +23,26 @@
 //
 // * [Compressor and Decompressor](compressor.html): compression and decompression of HEADER and
 //   PUSH_PROMISE frames
 //
 // * [Serializer and Deserializer](framer.html): the lowest layer in the stack that transforms
 //   between the binary and the JavaScript object representation of HTTP/2 frames
 //
 // [homepage]:            https://github.com/molnarg/node-http2
-// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-13
-// [http2-connheader]:    http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-3.5
-// [http2-stream]:        http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5
-// [http2-streamstate]:   http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5.1
+// [http2]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
+// [http2-connheader]:    http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-3.5
+// [http2-stream]:        http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5
+// [http2-streamstate]:   http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.1
 // [node]:                http://nodejs.org/
 // [node-stream]:         http://nodejs.org/api/stream.html
 // [node-https]:          http://nodejs.org/api/https.html
 // [node-http]:           http://nodejs.org/api/http.html
 
-exports.ImplementedVersion = 'h2-13';
+exports.ImplementedVersion = 'h2-14';
 
 exports.Endpoint = require('./endpoint').Endpoint;
 
 /* Bunyan serializers exported by submodules that are worth adding when creating a logger. */
 exports.serializers = {};
 var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint'];
 modules.map(require).forEach(function(module) {
   for (var name in module.serializers) {
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js
@@ -235,16 +235,20 @@ Stream.prototype._writeUpstream = functi
 
   var moreNeeded = Flow.prototype.write.call(this.upstream, frame);
 
   // * Transition to a new state if that's the effect of receiving the frame
   this._transition(false, frame);
 
   // * If it's a control frame. Call the appropriate handler method.
   if (frame.type === 'HEADERS') {
+    if (this._processedHeaders && !frame.flags['END_STREAM']) {
+      this.emit('error', 'PROTOCOL_ERROR');
+    }
+    this._processedHeaders = true;
     this._onHeaders(frame);
   } else if (frame.type === 'PUSH_PROMISE') {
     this._onPromise(frame);
   } else if (frame.type === 'PRIORITY') {
     this._onPriority(frame);
   } else if (frame.type === 'ALTSVC') {
     // TODO
   } else if (frame.type === 'BLOCKED') {
@@ -341,17 +345,17 @@ Stream.prototype._finishing = function _
     this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.');
     lastFrame.flags.END_STREAM = true;
     this._transition(true, endFrame);
   } else {
     this._pushUpstream(endFrame);
   }
 };
 
-// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-13#section-5.1)
+// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.1)
 // ----------------
 //
 //                           +--------+
 //                     PP    |        |    PP
 //                  ,--------|  idle  |--------.
 //                 /         |        |         \
 //                v          +--------+          v
 //         +----------+          |           +----------+
@@ -376,16 +380,17 @@ Stream.prototype._finishing = function _
 //                           +--------+
 
 // Streams begin in the IDLE state and transitions happen when there's an incoming or outgoing frame
 Stream.prototype._initializeState = function _initializeState() {
   this.state = 'IDLE';
   this._initiated = undefined;
   this._closedByUs = undefined;
   this._closedWithRst = undefined;
+  this._processedHeaders = false;
 };
 
 // Only `_setState` should change `this.state` directly. It also logs the state change and notifies
 // interested parties using the 'state' event.
 Stream.prototype._setState = function transition(state) {
   assert(this.state !== state);
   this._log.debug({ from: this.state, to: state }, 'State transition');
   this.state = state;
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/package.json
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/package.json
@@ -1,11 +1,11 @@
 {
   "name": "http2-protocol",
-  "version": "0.13.0",
+  "version": "0.14.1",
   "description": "A JavaScript implementation of the HTTP/2 framing layer",
   "main": "lib/index.js",
   "engines" : {
     "node" : ">=0.10.0"
   },
   "devDependencies": {
     "istanbul": "*",
     "chai": "*",
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/test/compressor.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/test/compressor.js
@@ -71,241 +71,289 @@ QWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJK
 QWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOPIUAXQWEO\
 IUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234LASDJKHQKBZXOQWEOP\
 IUAXQWEOIUAXLJKHQWOEIUALQWEOIUAXLQEUAXLLKJASDQWEOUIAXN1234AAAAAAAAAA\
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1234 m\
 ax-age=3600; version=1': '94e783f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f73c3bafe5cd8f666bbfbf9ab672c1ab5e4e10fe6ce583564e10fe67cb9b1ece5ab064e10e7d9cb06ac9c21fccfb307087f33e7cd961dd7f672c1ab86487f34844cb59e1dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab27087f33e5cd8f672d583270873ece583564e10fe67d983843f99f3e6cb0eebfb3960d5c3243f9a42265acf0eebf97363d99aefefe6ad9cb06ad793843f9b3960d593843f99f2e6c7b396ac1938439f672c1ab27087f33ecc1c21fccf9f3658775fd9cb06ae1921fcd21132d678775fcb9b1eccd77f7f356ce58356bc9c21fcd9cb06ac9c21fccf97363d9cb560c9c21cfb3960d593843f99f660e10fe67cf9b2c3bafece583570c90fe6908996a1861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861861842265a5291f9587316065c003ed4ee5b1063d5007f'
 };
 
 var test_headers = [{
-  // literal w/index, name index
+  // index
   header: {
     name: 1,
-    value: 'GET',
-    index: true,
+    value: 1,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('82', 'hex')
+}, {
+  // index
+  header: {
+    name: 5,
+    value: 5,
+    index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('42' + '03474554', 'hex')
+  buffer: new Buffer('86', 'hex')
+}, {
+  // index
+  header: {
+    name: 3,
+    value: 3,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('84', 'hex')
 }, {
   // literal w/index, name index
   header: {
-    name: 6,
-    value: 'http',
-    index: true,
-    mustNeverIndex: false,
-    contextUpdate: false,
-    clearReferenceSet: false,
-    newMaxSize: 0
-  },
-  buffer: new Buffer('47' + '839d29af', 'hex')
-}, {
-  // literal w/index, name index
-  header: {
-    name: 5,
-    value: '/',
-    index: true,
-    mustNeverIndex: false,
-    contextUpdate: false,
-    clearReferenceSet: false,
-    newMaxSize: 0
-  },
-  buffer: new Buffer('46' + '012F', 'hex')
-}, {
-  // literal w/index, name index
-  header: {
-    name: 3,
+    name: 0,
     value: 'www.foo.com',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('44' + '89f1e3c2f29ceb90f4ff', 'hex')
+  buffer: new Buffer('41' + '89f1e3c2f29ceb90f4ff', 'hex')
 }, {
-  // literal w/index, name index
-  header: {
-    name: 2,
-    value: 'https',
-    index: true,
-    mustNeverIndex: false,
-    contextUpdate: false,
-    clearReferenceSet: false,
-    newMaxSize: 0
-  },
-  buffer: new Buffer('43' + '849d29ad1f', 'hex')
-}, {
-  // literal w/index, name index
+  // indexed
   header: {
     name: 1,
-    value: 'www.bar.com',
-    index: true,
+    value: 1,
+    index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('42' + '89f1e3c2f18ec5c87a7f', 'hex')
+  buffer: new Buffer('82', 'hex')
 }, {
-  // literal w/index, name index
+  // indexed
   header: {
-    name: 29,
-    value: 'no-cache',
-    index: true,
+    name: 6,
+    value: 6,
+    index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('5e' + '86a8eb10649cbf', 'hex')
+  buffer: new Buffer('87', 'hex')
 }, {
   // indexed
   header: {
     name: 3,
     value: 3,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('84', 'hex')
 }, {
-  // indexed
+  // literal w/index, name index
+  header: {
+    name: 0,
+    value: 'www.bar.com',
+    index: true,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('41' + '89f1e3c2f18ec5c87a7f', 'hex')
+}, {
+  // literal w/index, name index
   header: {
-    name: 5,
-    value: 5,
+    name: 23,
+    value: 'no-cache',
+    index: true,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('58' + '86a8eb10649cbf', 'hex')
+}, {
+  // index
+  header: {
+    name: 1,
+    value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('86', 'hex')
+  buffer: new Buffer('82', 'hex')
+}, {
+  // index
+  header: {
+    name: 6,
+    value: 6,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('87', 'hex')
 }, {
   // literal w/index, name index
   header: {
-    name: 4,
+    name: 3,
     value: '/custom-path.css',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('45' + '8b6096a127a56ac699d72211', 'hex')
+  buffer: new Buffer('44' + '8b6096a127a56ac699d72211', 'hex')
+}, {
+  // index
+  header: {
+    name: 63,
+    value: 63,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('C0', 'hex')
 }, {
   // literal w/index, new name & value
   header: {
     name: 'custom-key',
     value: 'custom-value',
     index: true,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('40' + '8825a849e95ba97d7f' + '8925a849e95bb8e8b4bf', 'hex')
 }, {
-  // indexed
+  // index
   header: {
-    name: 2,
-    value: 2,
+    name: 1,
+    value: 1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
-  buffer: new Buffer('83', 'hex')
+  buffer: new Buffer('82', 'hex')
 }, {
-  // indexed
+  // index
   header: {
     name: 6,
     value: 6,
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('87', 'hex')
 }, {
+  // index
+  header: {
+    name: 62,
+    value: 62,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('BF', 'hex')
+}, {
+  // index
+  header: {
+    name: 65,
+    value: 65,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('C2', 'hex')
+}, {
+  // index
+  header: {
+    name: 64,
+    value: 64,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('C1', 'hex')
+}, {
+  // index
+  header: {
+    name: 61,
+    value: 61,
+    index: false,
+    mustNeverIndex: false,
+    contextUpdate: false,
+    newMaxSize: 0
+  },
+  buffer: new Buffer('BE', 'hex')
+}, {
   // Literal w/o index, name index
   header: {
     name: 6,
     value: "whatever",
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('07' + '86f138d25ee5b3', 'hex')
 }, {
   // Literal w/o index, new name & value
   header: {
     name: "foo",
     value: "bar",
     index: false,
     mustNeverIndex: false,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('00' + '8294e7' + '03626172', 'hex')
 }, {
   // Literal never indexed, name index
   header: {
     name: 6,
     value: "whatever",
     index: false,
     mustNeverIndex: true,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('17' + '86f138d25ee5b3', 'hex')
 }, {
   // Literal never indexed, new name & value
   header: {
     name: "foo",
     value: "bar",
     index: false,
     mustNeverIndex: true,
     contextUpdate: false,
-    clearReferenceSet: false,
     newMaxSize: 0
   },
   buffer: new Buffer('10' + '8294e7' + '03626172', 'hex')
 }, {
   header: {
     name: -1,
     value: -1,
     index: false,
     mustNeverIndex: false,
     contextUpdate: true,
-    clearReferenceSet: true,
-    newMaxSize: 0
-  },
-  buffer: new Buffer('30', 'hex')
-}, {
-  header: {
-    name: -1,
-    value: -1,
-    index: false,
-    mustNeverIndex: false,
-    contextUpdate: true,
-    clearReferenceSet: false,
     newMaxSize: 100
   },
-  buffer: new Buffer('2F55', 'hex')
+  buffer: new Buffer('3F45', 'hex')
 }];
 
 var test_header_sets = [{
   headers: {
     ':method': 'GET',
     ':scheme': 'http',
     ':path': '/',
     ':authority': 'www.foo.com'
@@ -323,34 +371,26 @@ var test_header_sets = [{
 }, {
   headers: {
     ':method': 'GET',
     ':scheme': 'https',
     ':path': '/custom-path.css',
     ':authority': 'www.bar.com',
     'custom-key': 'custom-value'
   },
-  buffer: util.concat(test_headers.slice(9, 13).map(function(test) { return test.buffer; }))
+  buffer: util.concat(test_headers.slice(9, 14).map(function(test) { return test.buffer; }))
 }, {
   headers: {
     ':method': 'GET',
     ':scheme': 'https',
     ':path': '/custom-path.css',
     ':authority': ['www.foo.com', 'www.bar.com'],
     'custom-key': 'custom-value'
   },
-  buffer: test_headers[3].buffer
-}, {
-  headers: {
-    ':status': '200',
-    'user-agent': 'my-user-agent',
-    'cookie': 'first; second; third; third; fourth',
-    'multiple': ['first', 'second', 'third', 'third; fourth'],
-    'verylong': (new Buffer(9000)).toString('hex')
-  }
+  buffer: util.concat(test_headers.slice(14, 19).map(function(test) { return test.buffer; }))
 }];
 
 describe('compressor.js', function() {
   describe('HeaderTable', function() {
   });
 
   describe('HuffmanTable', function() {
     describe('method encode(buffer)', function() {
--- a/testing/xpcshell/node-http2/node_modules/http2-protocol/test/framer.js
+++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/test/framer.js
@@ -16,302 +16,304 @@ var frame_types = {
   GOAWAY:        ['last_stream', 'error'],
   WINDOW_UPDATE: ['window_size'],
   CONTINUATION:  ['data']
 };
 
 var test_frames = [{
   frame: {
     type: 'DATA',
-    flags: { END_STREAM: false, END_SEGMENT: false, RESERVED4: false,
+    flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
              PADDED: false },
     stream: 10,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream +   content
-  buffer: new Buffer('0004' + '00' + '00' + '0000000A' +   '12345678', 'hex')
+  buffer: new Buffer('000004' + '00' + '00' + '0000000A' +   '12345678', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: false },
     stream: 15,
 
     data: new Buffer('12345678', 'hex')
   },
-  buffer: new Buffer('0004' + '01' + '00' + '0000000F' +   '12345678', 'hex')
+  buffer: new Buffer('000004' + '01' + '00' + '0000000F' +   '12345678', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: false,
 
     data: new Buffer('12345678', 'hex')
   },
-  buffer: new Buffer('0009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
+  buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '0000000A' + '05' + '12345678', 'hex')
 
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: false, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: true,
 
     data: new Buffer('12345678', 'hex')
   },
-  buffer: new Buffer('0009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
+  buffer: new Buffer('000009' + '01' + '20' + '0000000F' + '8000000A' + '05' + '12345678', 'hex')
 
 }, {
   frame: {
     type: 'PRIORITY',
     flags: { },
     stream: 10,
 
     priorityDependency: 9,
     priorityWeight: 5,
     exclusiveDependency: false
   },
-  buffer: new Buffer('0005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
+  buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '00000009' + '05', 'hex')
 
 }, {
   frame: {
     type: 'PRIORITY',
     flags: { },
     stream: 10,
 
     priorityDependency: 9,
     priorityWeight: 5,
     exclusiveDependency: true
   },
-  buffer: new Buffer('0005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
+  buffer: new Buffer('000005' + '02' + '00' + '0000000A' + '80000009' + '05', 'hex')
 
 }, {
   frame: {
     type: 'RST_STREAM',
     flags: { },
     stream: 10,
 
     error: 'INTERNAL_ERROR'
   },
-  buffer: new Buffer('0004' + '03' + '00' + '0000000A' +   '00000002', 'hex')
+  buffer: new Buffer('000004' + '03' + '00' + '0000000A' +   '00000002', 'hex')
 
 }, {
   frame: {
     type: 'SETTINGS',
     flags: { ACK: false },
     stream: 10,
 
     settings: {
       SETTINGS_HEADER_TABLE_SIZE: 0x12345678,
       SETTINGS_ENABLE_PUSH: true,
       SETTINGS_MAX_CONCURRENT_STREAMS: 0x01234567,
-      SETTINGS_INITIAL_WINDOW_SIZE:    0x89ABCDEF
+      SETTINGS_INITIAL_WINDOW_SIZE:    0x89ABCDEF,
+      SETTINGS_MAX_FRAME_SIZE:         0x00010000
     }
   },
-  buffer: new Buffer('0018' + '04' + '00' + '0000000A' +   '0001' + '12345678' +
-                                                           '0002' + '00000001' +
-                                                           '0003' + '01234567' +
-                                                           '0004' + '89ABCDEF', 'hex')
+  buffer: new Buffer('00001E' + '04' + '00' + '0000000A' +   '0001' + '12345678' +
+                                                             '0002' + '00000001' +
+                                                             '0003' + '01234567' +
+                                                             '0004' + '89ABCDEF' +
+                                                             '0005' + '00010000', 'hex')
 
 }, {
   frame: {
     type: 'PUSH_PROMISE',
     flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false,
              PADDED: false },
     stream: 15,
 
     promised_stream: 3,
     data: new Buffer('12345678', 'hex')
   },
-  buffer: new Buffer('0008' + '05' + '00' + '0000000F' +   '00000003' + '12345678', 'hex')
+  buffer: new Buffer('000008' + '05' + '00' + '0000000F' +   '00000003' + '12345678', 'hex')
 
 }, {
   frame: {
     type: 'PING',
     flags: { ACK: false },
     stream: 15,
 
     data: new Buffer('1234567887654321', 'hex')
   },
-  buffer: new Buffer('0008' + '06' + '00' + '0000000F' +   '1234567887654321', 'hex')
+  buffer: new Buffer('000008' + '06' + '00' + '0000000F' +   '1234567887654321', 'hex')
 
 }, {
   frame: {
     type: 'GOAWAY',
     flags: { },
     stream: 10,
 
     last_stream: 0x12345678,
     error: 'PROTOCOL_ERROR'
   },
-  buffer: new Buffer('0008' + '07' + '00' + '0000000A' +   '12345678' + '00000001', 'hex')
+  buffer: new Buffer('000008' + '07' + '00' + '0000000A' +   '12345678' + '00000001', 'hex')
 
 }, {
   frame: {
     type: 'WINDOW_UPDATE',
     flags: { },
     stream: 10,
 
     window_size: 0x12345678
   },
-  buffer: new Buffer('0004' + '08' + '00' + '0000000A' +   '12345678', 'hex')
+  buffer: new Buffer('000004' + '08' + '00' + '0000000A' +   '12345678', 'hex')
 }, {
   frame: {
     type: 'CONTINUATION',
     flags: { RESERVED1: false, RESERVED2: false, END_HEADERS: true },
     stream: 10,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream +   content
-  buffer: new Buffer('0004' + '09' + '04' + '0000000A' +   '12345678', 'hex')
+  buffer: new Buffer('000004' + '09' + '04' + '0000000A' +   '12345678', 'hex')
 }, {
   frame: {
     type: 'ALTSVC',
     flags: { },
     stream: 0,
 
     maxAge: 31536000,
     port: 4443,
     protocolID: "h2",
     host: "altsvc.example.com",
     origin: ""
   },
-  buffer: new Buffer('001D' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D', 'hex')
+  buffer: new Buffer('00001D' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D', 'hex')
 }, {
   frame: {
     type: 'ALTSVC',
     flags: { },
     stream: 0,
 
     maxAge: 31536000,
     port: 4443,
     protocolID: "h2",
     host: "altsvc.example.com",
     origin: "https://onlyme.example.com"
   },
-  buffer: new Buffer('0037' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D' + '68747470733A2F2F6F6E6C796D652E6578616D706C652E636F6D', 'hex')
+  buffer: new Buffer('000037' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D' + '68747470733A2F2F6F6E6C796D652E6578616D706C652E636F6D', 'hex')
 
 }, {
   frame: {
     type: 'BLOCKED',
     flags: { },
     stream: 10
   },
-  buffer: new Buffer('0000' + '0B' + '00' + '0000000A', 'hex')
+  buffer: new Buffer('000000' + '0B' + '00' + '0000000A', 'hex')
 }];
 
 var deserializer_test_frames = test_frames.slice(0);
 var padded_test_frames = [{
   frame: {
     type: 'DATA',
-    flags: { END_STREAM: false, END_SEGMENT: false, RESERVED4: false,
+    flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
              PADDED: true },
     stream: 10,
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + content + padding
-  buffer: new Buffer('000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
+  buffer: new Buffer('00000B' + '00' + '08' + '0000000A' + '06' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: false },
     stream: 15,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + data + padding
-  buffer: new Buffer('000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
+  buffer: new Buffer('00000B' + '01' + '08' + '0000000F' + '06' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: false,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
-  buffer: new Buffer('0010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
+  buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '0000000A' + '05' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'HEADERS',
-    flags: { END_STREAM: false, END_SEGMENT: false, END_HEADERS: false,
+    flags: { END_STREAM: false, RESERVED2: false, END_HEADERS: false,
              PADDED: true, RESERVED5: false, PRIORITY: true },
     stream: 15,
     priorityDependency: 10,
     priorityWeight: 5,
     exclusiveDependency: true,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + priority dependency + priority weight + data + padding
-  buffer: new Buffer('0010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
+  buffer: new Buffer('000010' + '01' + '28' + '0000000F' + '06' + '8000000A' + '05' + '12345678' + '000000000000', 'hex')
 
 }, {
   frame: {
     type: 'PUSH_PROMISE',
     flags: { RESERVED1: false, RESERVED2: false, END_PUSH_PROMISE: false,
              PADDED: true },
     stream: 15,
 
     promised_stream: 3,
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream + pad length + promised stream + data + padding
-  buffer: new Buffer('000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
+  buffer: new Buffer('00000F' + '05' + '08' + '0000000F' + '06' + '00000003' + '12345678' + '000000000000', 'hex')
 
 }];
 for (var idx = 0; idx < padded_test_frames.length; idx++) {
   deserializer_test_frames.push(padded_test_frames[idx]);
 }
 
 
 describe('framer.js', function() {
   describe('Serializer', function() {
     describe('static method .commonHeader({ type, flags, stream }, buffer_array)', function() {
-      it('should add the appropriate 8 byte header buffer in front of the others', function() {
+      it('should add the appropriate 9 byte header buffer in front of the others', function() {
         for (var i = 0; i < test_frames.length; i++) {
           var test = test_frames[i];
-          var buffers = [test.buffer.slice(8)];
-          var header_buffer = test.buffer.slice(0,8);
+          var buffers = [test.buffer.slice(9)];
+          var header_buffer = test.buffer.slice(0,9);
           Serializer.commonHeader(test.frame, buffers);
           expect(buffers[0]).to.deep.equal(header_buffer);
         }
       });
     });
 
     Object.keys(frame_types).forEach(function(type) {
       var tests = test_frames.filter(function(test) { return test.frame.type === type; });
       var frame_shape = '{ ' + frame_types[type].join(', ') + ' }';
       describe('static method .' + type + '(' + frame_shape + ', buffer_array)', function() {
         it('should push buffers to the array that make up a ' + type + ' type payload', function() {
           for (var i = 0; i < tests.length; i++) {
             var test = tests[i];
             var buffers = [];
             Serializer[type](test.frame, buffers);
-            expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(8));
+            expect(util.concat(buffers)).to.deep.equal(test.buffer.slice(9));
           }
         });
       });
     });
 
     describe('transform stream', function() {
       it('should transform frame objects to appropriate buffers', function() {
         var stream = new Serializer(util.log);
@@ -329,17 +331,17 @@ describe('framer.js', function() {
     });
   });
 
   describe('Deserializer', function() {
     describe('static method .commonHeader(header_buffer, frame)', function() {
       it('should augment the frame object with these properties: { type, flags, stream })', function() {
         for (var i = 0; i < deserializer_test_frames.length; i++) {
           var test = deserializer_test_frames[i], frame = {};
-          Deserializer.commonHeader(test.buffer.slice(0,8), frame);
+          Deserializer.commonHeader(test.buffer.slice(0,9), frame);
           expect(frame).to.deep.equal({
             type:   test.frame.type,
             flags:  test.frame.flags,
             stream: test.frame.stream
           });
         }
       });
     });
@@ -351,17 +353,17 @@ describe('framer.js', function() {
         it('should augment the frame object with these properties: ' + frame_shape, function() {
           for (var i = 0; i < tests.length; i++) {
             var test = tests[i];
             var frame = {
               type:   test.frame.type,
               flags:  test.frame.flags,
               stream: test.frame.stream
             };
-            Deserializer[type](test.buffer.slice(8), frame);
+            Deserializer[type](test.buffer.slice(9), frame);
             expect(frame).to.deep.equal(test.frame);
           }
         });
       });
     });
 
     describe('transform stream', function() {
       it('should transform buffers to appropriate frame object', function() {
--- a/testing/xpcshell/node-http2/package.json
+++ b/testing/xpcshell/node-http2/package.json
@@ -1,18 +1,18 @@
 {
   "name": "http2",
-  "version": "2.6.0",
+  "version": "2.7.1",
   "description": "An HTTP/2 client and server implementation",
   "main": "lib/index.js",
   "engines" : {
     "node" : ">=0.10.19"
   },
   "dependencies": {
-    "http2-protocol": ">=0.13.0"
+    "http2-protocol": ">=0.14.1"
   },
   "devDependencies": {
     "istanbul": "*",
     "chai": "*",
     "mocha": "*",
     "docco": "*",
     "bunyan": "*"
   },
--- a/testing/xpcshell/node-http2/test/http.js
+++ b/testing/xpcshell/node-http2/test/http.js
@@ -80,17 +80,17 @@ describe('http.js', function() {
       request.stream = { reset: util.noop };
       request[name].apply(request, originalArguments);
       delete request.stream;
 
       // When in fallback mode, this call should be forwarded
       request[name].apply(request, originalArguments);
       var mockFallbackRequest = { on: util.noop };
       mockFallbackRequest[name] = function() {
-        expect(arguments).to.deep.equal(originalArguments);
+        expect(Array.prototype.slice.call(arguments)).to.deep.equal(originalArguments);
         done();
       };
       request._fallback(mockFallbackRequest);
     }
     describe('method `setNoDelay(noDelay)`', function() {
       it('should act as a proxy for the backing HTTPS agent\'s `setNoDelay` method', function(done) {
         testFallbackProxyMethod('setNoDelay', [true], done);
       });
@@ -106,16 +106,32 @@ describe('http.js', function() {
       });
     });
     describe('method `abort()`', function() {
       it('should act as a proxy for the backing HTTPS agent\'s `abort` method', function(done) {
         testFallbackProxyMethod('abort', [], done);
       });
     });
   });
+  describe('OutgoingResponse', function() {
+    it('should throw error when writeHead is called multiple times on it', function() {
+      var called = false;
+      var stream = { _log: util.log, headers: function () {
+        if (called) {
+          throw new Error('Should not send headers twice');
+        } else {
+          called = true;
+        }
+      }, once: util.noop };
+      var response = new http2.OutgoingResponse(stream);
+
+      response.writeHead(200);
+      response.writeHead(404)
+    });
+  });
   describe('test scenario', function() {
     describe('simple request', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
         var server = http2.createServer(options, function(request, response) {
           expect(request.url).to.equal(path);