Bug 1296280 (part 0a) - Update to node-http2 3.3.6 r?mcmanus draft
authorNicholas Hurley <hurley@todesschaf.org>
Fri, 16 Sep 2016 08:54:03 -0700
changeset 414560 d4e2540386dc8a3e5a892d9c304c300173d2a262
parent 411107 95acb9299fafdc69463c49860caf367e4fbcc8e3
child 414561 6a52d9c83f9755bac19c27d4afd227f4197b33a2
push id29701
push userbmo:hurley@todesschaf.org
push dateFri, 16 Sep 2016 16:04:45 +0000
reviewersmcmanus
bugs1296280
milestone51.0a1
Bug 1296280 (part 0a) - Update to node-http2 3.3.6 r?mcmanus MozReview-Commit-ID: 6c5RrFFDak0
testing/xpcshell/node-http2/.gitignore
testing/xpcshell/node-http2/.travis.yml
testing/xpcshell/node-http2/HISTORY.md
testing/xpcshell/node-http2/LICENSE
testing/xpcshell/node-http2/README.md
testing/xpcshell/node-http2/example/client.js
testing/xpcshell/node-http2/example/localhost.crt
testing/xpcshell/node-http2/example/localhost.key
testing/xpcshell/node-http2/example/server.js
testing/xpcshell/node-http2/lib/http.js
testing/xpcshell/node-http2/lib/index.js
testing/xpcshell/node-http2/lib/protocol/compressor.js
testing/xpcshell/node-http2/lib/protocol/connection.js
testing/xpcshell/node-http2/lib/protocol/flow.js
testing/xpcshell/node-http2/lib/protocol/framer.js
testing/xpcshell/node-http2/lib/protocol/index.js
testing/xpcshell/node-http2/lib/protocol/stream.js
testing/xpcshell/node-http2/package.json
testing/xpcshell/node-http2/test/flow.js
testing/xpcshell/node-http2/test/http.js
testing/xpcshell/node-http2/test/stream.js
--- a/testing/xpcshell/node-http2/.gitignore
+++ b/testing/xpcshell/node-http2/.gitignore
@@ -1,4 +1,7 @@
 node_modules
 .idea
 coverage
 doc
+.vscode/.browse*
+npm-debug.log
+typings
\ No newline at end of file
--- a/testing/xpcshell/node-http2/.travis.yml
+++ b/testing/xpcshell/node-http2/.travis.yml
@@ -1,5 +1,5 @@
  language: node_js
  node_js:
-   - "0.11"
-   - "0.10"
+   - "iojs"
+   - "0.12"
 
--- a/testing/xpcshell/node-http2/HISTORY.md
+++ b/testing/xpcshell/node-http2/HISTORY.md
@@ -1,11 +1,36 @@
 Version history
 ===============
 
+### 3.3.6 (2016-09-16) ###
+* We were not appropriately sending HPACK context updates when receiving SETTINGS_HEADER_TABLE_SIZE. This release fixes that bug.
+
+### 3.3.5 (2016-09-06) ###
+* Fix issues with large DATA frames (https://github.com/molnarg/node-http2/issues/207)
+
+### 3.3.4 (2016-04-22) ###
+* More PR bugfixes (https://github.com/molnarg/node-http2/issues?q=milestone%3Av3.3.4)
+
+### 3.3.3 (2016-04-21) ###
+
+* Bugfixes from pull requests (https://github.com/molnarg/node-http2/search?q=milestone%3Av3.3.3&type=Issues&utf8=%E2%9C%93)
+
+### 3.3.2 (2016-01-11) ###
+
+* Fix an incompatibility with Firefox (issue 167)
+
+### 3.3.1 (2016-01-11) ###
+
+* Fix some DoS bugs (issues 145, 146, 147, and 148)
+
+### 3.3.0 (2016-01-10) ###
+
+* Bugfix updates from pull requests
+
 ### 3.2.0 (2015-02-19) ###
 
 * Update ALPN token to final RFC version (h2).
 * Update altsvc implementation to draft 06: [draft-ietf-httpbis-alt-svc-06]
 
 [draft-ietf-httpbis-altsvc-06]: http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
 
 ### 3.1.2 (2015-02-17) ###
--- a/testing/xpcshell/node-http2/LICENSE
+++ b/testing/xpcshell/node-http2/LICENSE
@@ -1,11 +1,11 @@
 The MIT License
 
-Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
+Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>, Google Inc
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 'Software'), to deal in the Software without restriction, including
 without limitation the rights to use, copy, modify, merge, publish,
 distribute, sublicense, and/or sell copies of the Software, and to
 permit persons to whom the Software is furnished to do so, subject to
 the following conditions:
@@ -15,9 +15,8 @@ included in all copies or substantial po
 
 THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
--- a/testing/xpcshell/node-http2/README.md
+++ b/testing/xpcshell/node-http2/README.md
@@ -1,12 +1,12 @@
 node-http2
 ==========
 
-An HTTP/2 ([draft-ietf-httpbis-http2-16](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16))
+An HTTP/2 ([RFC 7540](http://tools.ietf.org/html/rfc7540))
 client and server implementation for node.js.
 
 ![Travis CI status](https://travis-ci.org/molnarg/node-http2.svg?branch=master)
 
 Installation
 ------------
 
 ```
@@ -36,18 +36,16 @@ var options = {
 require('http2').createServer(options, function(request, response) {
   response.end('Hello world!');
 }).listen(8080);
 ```
 
 ### Using as a client ###
 
 ```javascript
-process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
-
 require('http2').get('https://localhost:8080/', function(response) {
   response.pipe(process.stdout);
 });
 ```
 
 ### Simple static file server ###
 
 An simple static file server serving up content from its own directory is available in the `example`
@@ -69,20 +67,17 @@ An example client is also available. Dow
 
 For a server push example, see the source code of the example
 [server](https://github.com/molnarg/node-http2/blob/master/example/server.js) and
 [client](https://github.com/molnarg/node-http2/blob/master/example/client.js).
 
 Status
 ------
 
-* ALPN is not yet supported in node.js (see
-  [this issue](https://github.com/joyent/node/issues/5945)). For ALPN support, you will have to use
-  [Shigeki Ohtsu's node.js fork](https://github.com/shigeki/node/tree/alpn_support) until this code
-  gets merged upstream.
+* ALPN is only supported in node.js >= 5.0
 * Upgrade mechanism to start HTTP/2 over unencrypted channel is not implemented yet
   (issue [#4](https://github.com/molnarg/node-http2/issues/4))
 * Other minor features found in
   [this list](https://github.com/molnarg/node-http2/issues?labels=feature) are not implemented yet
 
 Development
 -----------
 
@@ -140,17 +135,17 @@ into a file, then the log output is in b
 
 Running the example server and client with `info` level logging output:
 
 ```bash
 $ HTTP2_LOG=info node ./example/server.js
 ```
 
 ```bash
-$ HTTP2_LOG=info node ./example/client.js 'http://localhost:8080/server.js' >/dev/null
+$ HTTP2_LOG=info node ./example/client.js 'https://localhost:8080/server.js' >/dev/null
 ```
 
 Contributors
 ------------
 
 The co-maintainer of the project is [Nick Hurley](https://github.com/todesschaf).
 
 Code contributions are always welcome! People who contributed to node-http2 so far:
@@ -160,17 +155,17 @@ Code contributions are always welcome! P
 * [Yoshihiro Iwanaga](https://github.com/iwanaga)
 * [Igor Novikov](https://github.com/vsemogutor)
 * [James Willcox](https://github.com/snorp)
 * [David Björklund](https://github.com/kesla)
 * [Patrick McManus](https://github.com/mcmanus)
 
 Special thanks to Google for financing the development of this module as part of their [Summer of
 Code program](https://developers.google.com/open-source/soc/) (project: [HTTP/2 prototype server
-implementation](https://google-melange.appspot.com/gsoc/project/google/gsoc2013/molnarg/5001)), and
+implementation](https://google-melange.appspot.com/gsoc/project/details/google/gsoc2013/molnarg/5818821692620800)), and
 Nick Hurley of Mozilla, my GSoC mentor, who helped with regular code review and technical advices.
 
 License
 -------
 
 The MIT License
 
 Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>
--- a/testing/xpcshell/node-http2/example/client.js
+++ b/testing/xpcshell/node-http2/example/client.js
@@ -1,23 +1,30 @@
 var fs = require('fs');
 var path = require('path');
 var http2 = require('..');
+var urlParse = require('url').parse;
 
 // Setting the global logger (optional)
 http2.globalAgent = new http2.Agent({
+  rejectUnauthorized: true,
   log: require('../test/util').createLogger('client')
 });
 
-// We use self signed certs in the example code so we ignore cert errors
-process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
-
 // Sending the request
 var url = process.argv.pop();
-var request = process.env.HTTP2_PLAIN ? http2.raw.get(url) : http2.get(url);
+var options = urlParse(url);
+
+// Optionally verify self-signed certificates.
+if (options.hostname == 'localhost') {
+  options.key = fs.readFileSync(path.join(__dirname, '/localhost.key'));
+  options.ca = fs.readFileSync(path.join(__dirname, '/localhost.crt'));
+}
+
+var request = process.env.HTTP2_PLAIN ? http2.raw.get(options) : http2.get(options);
 
 // Receiving the response
 request.on('response', function(response) {
   response.pipe(process.stdout);
   response.on('end', finish);
 });
 
 // Receiving push streams
--- a/testing/xpcshell/node-http2/example/localhost.crt
+++ b/testing/xpcshell/node-http2/example/localhost.crt
@@ -1,14 +1,14 @@
 -----BEGIN CERTIFICATE-----
-MIICIzCCAYwCCQCsvG34Az33qTANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJY
-WDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBh
-bnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTMwODAyMTMwODQzWhcNMTMw
-OTAxMTMwODQzWjBWMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5
-MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMRIwEAYDVQQDDAlsb2NhbGhv
-c3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM8D4tgE1cdI9uLo4N9AL8Ck
-ogREH5LSm3SsRGFdUu5b2Nx63K/qwtTUbtUlISZBI+KESkwQXcf1ErwXUDnbTtk/
-VpLJ+gfIN18e9LAdiZgAMEWlitiLhR+D17w4NzHYOpWy1YzgOckukPy1ZfTH9e7j
-tEH9+7c4mpv7QMkFdw4hAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAP+ZFskjJtNxY
-c+5JfMjEgSHEIy+AJ5/vXIspNYKMb7l0gYDvmFm8QTKChKTYvJmepBrIdL7MjXCX
-SWiPz05ch99c84yOx5qVpcPd0y2fjO8xn2NCLfWdP7iSVYmpftwzjqFzPc4EkAny
-NOpbnw9iM4JXsZNFtPTvSp+8StPGWzU=
+MIICDTCCAXYCCQC7iiBVXeTv1DANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJI
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTETMBEGA1UEChMKbm9kZS1odHRwMjESMBAG
+A1UEAxMJbG9jYWxob3N0MB4XDTE0MTIwMjE4NDcwNFoXDTI0MTEyOTE4NDcwNFow
+SzELMAkGA1UEBhMCSFUxEzARBgNVBAgTClNvbWUtU3RhdGUxEzARBgNVBAoTCm5v
+ZGUtaHR0cDIxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA8As7rj7xdD+RuAmORju9NI+jtOScGgiAbfovaFyzTu0O0H9SCExi
+u6e2iXMRfzomTix/yjRvbdHEXfgONG1MnKUc0oC4GxHXshyMDEXq9LadgAmR/nDL
+UVT0eo7KqC21ufaca2nVS9qOdlSCE/p7IJdb2+BF1RmuC9pHpXvFW20CAwEAATAN
+BgkqhkiG9w0BAQUFAAOBgQDn8c/9ho9L08dOqEJ2WTBmv4dfRC3oTWR/0oIGsaXb
+RhQONy5CJv/ymPYE7nCFWTMaia+w8oFqMie/aNZ7VK6L+hafuUS93IjuTXVN++JP
+4948B0BBagvXGTwNtvm/1sZHLrXTkH1dbRUEF8M+KUSRUu2zJgm+e1bD8WTKQOIL
+NA==
 -----END CERTIFICATE-----
--- a/testing/xpcshell/node-http2/example/localhost.key
+++ b/testing/xpcshell/node-http2/example/localhost.key
@@ -1,15 +1,15 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDPA+LYBNXHSPbi6ODfQC/ApKIERB+S0pt0rERhXVLuW9jcetyv
-6sLU1G7VJSEmQSPihEpMEF3H9RK8F1A5207ZP1aSyfoHyDdfHvSwHYmYADBFpYrY
-i4Ufg9e8ODcx2DqVstWM4DnJLpD8tWX0x/Xu47RB/fu3OJqb+0DJBXcOIQIDAQAB
-AoGAHtRVVBZkP+l92w0TcCv+8JGUD06V5Se4Pwfopxde4mCLS0qA0zIDEe8REm0V
-Ir1Quss4xVsqnDzDLX/LUtJ2S1+seWcoLdDV/wSDiM2CLS7KauUazrTWHLNId/lu
-/VombYWK10uNiDZZJ8xwEaKt+ZptC2kK8/yi0aX0PrGhAIECQQDsD8A64BBrWCrb
-7PrJt04CAcM3uBUzS6ausiJKw9IEktnvcnsN9kZazcAW86WDFsXI5oPubmgHhQ/s
-m9iIrbMPAkEA4IAUWi5mVuWAyUIc9YbjJdnmvkAykSxr/vp/26RMSDmUAAUlYNNc
-HZbM1uVZsFForKza28Px01Ga728ZdhRrzwJBAIrwNlcwu9lCWm95Cp6hGfPKb8ki
-uq+nTiKyS8avfLQebtElE1JDamNViEK6AuemBqFZM7upFeefJKFBlO/VNHcCQCXN
-CyBALdU14aCBtFSXOMoXzaV9M8aD/084qKy4FmwW3de/BhMuo5UL3kPU7Gwm2QQy
-OsvES4S0ee0U/OmH+LsCQAnNdxNPgzJDTx7wOTFhHIBr4mtepLiaRXIdkLEsR9Kb
-vcK6BwUfomM29eGOXtUAU7sJ5xnyKkSuNN7fxIWjzPI=
+MIICXQIBAAKBgQDwCzuuPvF0P5G4CY5GO700j6O05JwaCIBt+i9oXLNO7Q7Qf1II
+TGK7p7aJcxF/OiZOLH/KNG9t0cRd+A40bUycpRzSgLgbEdeyHIwMRer0tp2ACZH+
+cMtRVPR6jsqoLbW59pxradVL2o52VIIT+nsgl1vb4EXVGa4L2kele8VbbQIDAQAB
+AoGAKKB+FVup2hb4PsG/RrvNphu5hWA721wdAIAbjfpCjtUocLlb1PO4sjIMfu7u
+wy3AVfLKHhsJ0Phz18OoA8+L65NMoMRsHOGaLEnGIJzJcnDLT5+uTFN5di0a1+UK
+BzB828rlHBNoQisogVCoKTYlCPJAZuI3trEzupWAV28XjTECQQD5LUEwYq4xr62L
+dEq5Qj/+c5paK/jrEBY83VZUmWzYsFgUwmpdku2ITRILQlOM33j6rk8krZZb93sb
+38ydmfwjAkEA9p30zyjOI9kKqTl9WdYNYtIXpyNGYa+Pga33o9pawTewiyS2uCYs
+wnQQV26bQ0YwQqLQhtIbo4fzCO6Ex0w7LwJBANHNbd8cp4kEX35U+3nDM3i+w477
+CUp6sA6tWrw+tqw4xuEr1T1WshOauP+r6AdsPkPsMo0yb7CdzxVoObPVbLsCQQCc
+sx0cjEb/TCeUAy186Z+zzN6umqFb7Jt4wLt7Z4EHCIWqw/c95zPFks3XYDZTdsOv
+c5igMdzR+c4ZPMUthWiNAkByx7If12G1Z/R2Y0vIB0WJq4BJnZCZ0mRR0oAmPoA+
+sZbmwctZ3IU+68Rgr4EAhrU04ygjF67IiNyXX0qqu3VH
 -----END RSA PRIVATE KEY-----
--- a/testing/xpcshell/node-http2/example/server.js
+++ b/testing/xpcshell/node-http2/example/server.js
@@ -19,24 +19,36 @@ function onRequest(request, response) {
       push.writeHead(200);
       fs.createReadStream(path.join(__dirname, '/client.js')).pipe(push);
     }
     response.end(cachedFile);
   }
 
   // Reading file from disk if it exists and is safe.
   else if ((filename.indexOf(__dirname) === 0) && fs.existsSync(filename) && fs.statSync(filename).isFile()) {
-    response.writeHead('200');
+    response.writeHead(200);
+    var fileStream = fs.createReadStream(filename);
+    fileStream.pipe(response);
+    fileStream.on('finish',response.end);
+  }
 
-    fs.createReadStream(filename).pipe(response);
+  // Example for testing large (boundary-sized) frames.
+  else if (request.url === "/largeframe") {
+    response.writeHead(200);
+    var body = 'a';
+    for (var i = 0; i < 14; i++) {
+      body += body;
+    }
+    body = body + 'a';
+    response.end(body);
   }
 
   // Otherwise responding with 404.
   else {
-    response.writeHead('404');
+    response.writeHead(404);
     response.end();
   }
 }
 
 // Creating a bunyan logger (optional)
 var log = require('../test/util').createLogger('server');
 
 // Creating the server in plain or TLS mode (TLS mode is the default)
--- a/testing/xpcshell/node-http2/lib/http.js
+++ b/testing/xpcshell/node-http2/lib/http.js
@@ -6,58 +6,58 @@
 // additional features of HTTP/2 are exposed as extensions to this API. Furthermore, node-http2
 // should fall back to using HTTP/1.1 if needed. Compatibility with undocumented or deprecated
 // elements of the node.js HTTP/HTTPS API is a non-goal.
 //
 // Additional and modified API elements
 // ------------------------------------
 //
 // - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation
-//   see the [lib/endpoint.js](endpoint.html) file.
+//   see [protocol/endpoint.js](protocol/endpoint.html).
 //
 // - **Class: http2.Server**
 //   - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of
-//     HTTP/2 was successful: the reference to the [Endpoint](endpoint.html) object tied to the
+//     HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the
 //     socket.
 //
 // - **http2.createServer(options, [requestListener])**: additional option:
 //   - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object
-//   - **plain**: if `true`, the server will accept HTTP/2 connections over plain TCP instead of
-//     TLS
 //
 // - **Class: http2.ServerResponse**
 //   - **response.push(options)**: initiates a server push. `options` describes the 'imaginary'
 //     request to which the push stream is a response; the possible options are identical to the
 //     ones accepted by `http2.request`. Returns a ServerResponse object that can be used to send
 //     the response headers and content.
 //
 // - **Class: http2.Agent**
 //   - **new Agent(options)**: additional option:
 //     - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object
 //   - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests.
-//   - **agent.endpoints**: contains [Endpoint](endpoint.html) objects for HTTP/2 connections.
+//   - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections.
 //
-// - **http2.request(options, [callback])**: additional option:
-//   - **plain**: if `true`, the client will not try to build a TLS tunnel, instead it will use
-//     the raw TCP stream for HTTP/2
+// - **http2.request(options, [callback])**:
+//   - similar to http.request
+//
+// - **http2.get(options, [callback])**:
+//   - similar to http.get
 //
 // - **Class: http2.ClientRequest**
 //   - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference
-//     to the associated [HTTP/2 Stream](stream.html) object (and not to the TCP socket).
+//     to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket).
 //   - **Event: 'push' (promise)**: signals the intention of a server push associated to this
 //     request. `promise` is an IncomingPromise. If there's no listener for this event, the server
 //     push is cancelled.
 //   - **request.setPriority(priority)**: assign a priority to this request. `priority` is a number
 //     between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30.
 //
 // - **Class: http2.IncomingMessage**
 //   - has two subclasses for easier interface description: **IncomingRequest** and
 //     **IncomingResponse**
 //   - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated
-//     [HTTP/2 Stream](stream.html) object (and not to the TCP socket).
+//     [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket).
 //
 // - **Class: http2.IncomingRequest (IncomingMessage)**
 //   - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the
 //     path, and never a full url (it contains the path in most cases in the HTTPS api as well).
 //   - **message.scheme**: additional field. Mandatory HTTP/2 request metadata.
 //   - **message.host**: additional field. Mandatory HTTP/2 request metadata. Note that this
 //     replaces the old Host header field, but node-http2 will add Host to the `message.headers` for
 //     backwards compatibility.
@@ -82,21 +82,21 @@
 // API elements that are not applicable to HTTP/2
 // ----------------------------------------------
 //
 // The reason may be deprecation of certain HTTP/1.1 features, or that some API elements simply
 // don't make sense when using HTTP/2. These will not be present when a request is done with HTTP/2,
 // but will function normally when falling back to using HTTP/1.1.
 //
 // - **Class: http2.Server**
-//   - **Event: 'checkContinue'**: not in the spec, yet (see [http-spec#18][expect-continue])
+//   - **Event: 'checkContinue'**: not in the spec
 //   - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2
 //   - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive
 //     (PING frames)
-//   - **Event: 'connect'**: not in the spec, yet (see [http-spec#230][connect])
+//   - **Event: 'connect'**: not yet supported
 //   - **server.setTimeout(msecs, [callback])**
 //   - **server.timeout**
 //
 // - **Class: http2.ServerResponse**
 //   - **Event: 'close'**
 //   - **Event: 'timeout'**
 //   - **response.writeContinue()**
 //   - **response.writeHead(statusCode, [reasonPhrase], [headers])**: reasonPhrase will always be
@@ -114,21 +114,19 @@
 //   - **request.setTimeout(timeout, [callback])**
 //   - **request.setNoDelay([noDelay])**
 //   - **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-16#section-8.1.2.4
-// [expect-continue]: https://github.com/http2/http2-spec/issues/18
-// [connect]: https://github.com/http2/http2-spec/issues/230
+// [1]: https://nodejs.org/api/https.html
+// [2]: https://nodejs.org/api/http.html
+// [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4
 
 // Common server and client side code
 // ==================================
 
 var net = require('net');
 var url = require('url');
 var util = require('util');
 var EventEmitter = require('events').EventEmitter;
@@ -145,25 +143,24 @@ exports.IncomingMessage = IncomingMessag
 exports.OutgoingMessage = OutgoingMessage;
 exports.protocol = protocol;
 
 var deprecatedHeaders = [
   'connection',
   'host',
   'keep-alive',
   'proxy-connection',
-  'te',
   'transfer-encoding',
   'upgrade'
 ];
 
 // When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback
 var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0'];
 
-// Ciphersuite list based on the recommendations of http://wiki.mozilla.org/Security/Server_Side_TLS
+// Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS
 // The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites
 var cipherSuites = [
   'ECDHE-RSA-AES128-GCM-SHA256',
   'ECDHE-ECDSA-AES128-GCM-SHA256',
   'ECDHE-RSA-AES256-GCM-SHA384',
   'ECDHE-ECDSA-AES256-GCM-SHA384',
   'DHE-RSA-AES128-GCM-SHA256',
   'DHE-DSS-AES128-GCM-SHA256',
@@ -217,17 +214,17 @@ var defaultLogger = {
 
 // Bunyan serializers exported by submodules that are worth adding when creating a logger.
 exports.serializers = protocol.serializers;
 
 // IncomingMessage class
 // ---------------------
 
 function IncomingMessage(stream) {
-  // * This is basically a read-only wrapper for the [Stream](stream.html) class.
+  // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
   PassThrough.call(this);
   stream.pipe(this);
   this.socket = this.stream = stream;
 
   this._log = stream._log.child({ component: 'http' });
 
   // * HTTP/2.0 does not define a way to carry the version identifier that is included in the
   //   HTTP/1.1 request/status line. Version is always 2.0.
@@ -241,28 +238,32 @@ 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-16#section-8.1.2.3)
+// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3)
 // * `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`
   for (var name in headers) {
     if (name[0] !== ':') {
-      this.headers[name] = headers[name];
+      if (name === 'set-cookie' && !Array.isArray(headers[name])) {
+        this.headers[name] = [headers[name]];
+      } else {
+        this.headers[name] = headers[name];
+      }
     }
   }
 
   // * The last header block, if it's not the first, will represent the trailers
   var self = this;
   this.stream.on('headers', function(headers) {
     self._lastHeadersSeen = headers;
   });
@@ -280,22 +281,23 @@ IncomingMessage.prototype._checkSpecialH
     this.stream.reset('PROTOCOL_ERROR');
   }
 
   return value;
 };
 
 IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) {
   // * An HTTP/2.0 request or response MUST NOT include any of the following header fields:
-  //   Connection, Host, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, and Upgrade. A server
+  //   Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server
   //   MUST treat the presence of any of these header fields as a stream error of type
   //   PROTOCOL_ERROR.
+  //  If the TE header is present, it's only valid value is 'trailers'
   for (var i = 0; i < deprecatedHeaders.length; i++) {
     var key = deprecatedHeaders[i];
-    if (key in headers) {
+    if (key in headers || (key === 'te' && headers[key] !== 'trailers')) {
       this._log.error({ key: key, value: headers[key] }, 'Deprecated header found');
       this.stream.reset('PROTOCOL_ERROR');
       return;
     }
   }
 
   for (var headerName in headers) {
     // * Empty header name field is malformed
@@ -312,22 +314,23 @@ IncomingMessage.prototype._validateHeade
     }
   }
 };
 
 // OutgoingMessage class
 // ---------------------
 
 function OutgoingMessage() {
-  // * This is basically a read-only wrapper for the [Stream](stream.html) class.
+  // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
   Writable.call(this);
 
   this._headers = {};
   this._trailers = undefined;
   this.headersSent = false;
+  this.finished = false;
 
   this.on('finish', this._finish);
 }
 OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } });
 
 OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) {
   if (this.stream) {
     this.stream.write(chunk, encoding, callback);
@@ -340,37 +343,38 @@ OutgoingMessage.prototype._finish = func
   if (this.stream) {
     if (this._trailers) {
       if (this.request) {
         this.request.addTrailers(this._trailers);
       } else {
         this.stream.headers(this._trailers);
       }
     }
+    this.finished = true;
     this.stream.end();
   } else {
     this.once('socket', this._finish.bind(this));
   }
 };
 
 OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
   if (this.headersSent) {
-    throw new Error('Can\'t set headers after they are sent.');
+    return this.emit('error', new Error('Can\'t set headers after they are sent.'));
   } else {
     name = name.toLowerCase();
     if (deprecatedHeaders.indexOf(name) !== -1) {
-      throw new Error('Cannot set deprecated header: ' + name);
+      return this.emit('error', new Error('Cannot set deprecated header: ' + name));
     }
     this._headers[name] = value;
   }
 };
 
 OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
   if (this.headersSent) {
-    throw new Error('Can\'t remove headers after they are sent.');
+    return this.emit('error', new Error('Can\'t remove headers after they are sent.'));
   } else {
     delete this._headers[name.toLowerCase()];
   }
 };
 
 OutgoingMessage.prototype.getHeader = function getHeader(name) {
   return this._headers[name.toLowerCase()];
 };
@@ -386,16 +390,46 @@ OutgoingMessage.prototype._checkSpecialH
 // Server side
 // ===========
 
 exports.Server = Server;
 exports.IncomingRequest = IncomingRequest;
 exports.OutgoingResponse = OutgoingResponse;
 exports.ServerResponse = OutgoingResponse; // for API compatibility
 
+// Forward events `event` on `source` to all listeners on `target`.
+//
+// Note: The calling context is `source`.
+function forwardEvent(event, source, target) {
+  function forward() {
+    var listeners = target.listeners(event);
+
+    var n = listeners.length;
+
+    // Special case for `error` event with no listeners.
+    if (n === 0 && event === 'error') {
+      var args = [event];
+      args.push.apply(args, arguments);
+
+      target.emit.apply(target, args);
+      return;
+    }
+
+    for (var i = 0; i < n; ++i) {
+      listeners[i].apply(source, arguments);
+    }
+  }
+
+  source.on(event, forward);
+
+  // A reference to the function is necessary to be able to stop
+  // forwarding.
+  return forward;
+}
+
 // Server class
 // ------------
 
 function Server(options) {
   options = util._extend({}, options);
 
   this._log = (options.log || defaultLogger).child({ component: 'http' });
   this._settings = options.settings;
@@ -411,23 +445,28 @@ function Server(options) {
     options.NPNProtocols = supportedProtocols;
     options.ciphers = options.ciphers || cipherSuites;
     options.honorCipherOrder = (options.honorCipherOrder != false);
     this._server = https.createServer(options);
     this._originalSocketListeners = this._server.listeners('secureConnection');
     this._server.removeAllListeners('secureConnection');
     this._server.on('secureConnection', function(socket) {
       var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
-      if ((negotiatedProtocol === protocol.VERSION) && socket.servername) {
+      // It's true that the client MUST use SNI, but if it doesn't, we don't care, don't fall back to HTTP/1,
+      // since if the ALPN negotiation is otherwise successful, the client thinks we speak HTTP/2 but we don't.
+      if (negotiatedProtocol === protocol.VERSION) {
         start(socket);
       } else {
         fallback(socket);
       }
     });
     this._server.on('request', this.emit.bind(this, 'request'));
+
+    forwardEvent('error', this._server, this);
+    forwardEvent('listening', this._server, this);
   }
 
   // HTTP2 over plain TCP
   else if (options.plain) {
     this._log.info('Creating HTTP/2 server over plain TCP');
     this._mode = 'plain';
     this._server = net.createServer(start);
   }
@@ -453,16 +492,21 @@ Server.prototype._start = function _star
 
   endpoint.pipe(socket).pipe(endpoint);
 
   var self = this;
   endpoint.on('stream', function _onStream(stream) {
     var response = new OutgoingResponse(stream);
     var request = new IncomingRequest(stream);
 
+    // Some conformance to Node.js Https specs allows to distinguish clients:
+    request.remoteAddress = socket.remoteAddress;
+    request.remotePort = socket.remotePort;
+    request.connection = request.socket = response.socket = socket;
+
     request.once('ready', self.emit.bind(self, 'request', request, response));
   });
 
   endpoint.on('error', this.emit.bind(this, 'clientError'));
   socket.on('error', this.emit.bind(this, 'clientError'));
 
   this.emit('connection', socket, endpoint);
 };
@@ -479,21 +523,23 @@ Server.prototype._fallback = function _f
     this._originalSocketListeners[i].call(this._server, socket);
   }
 
   this.emit('connection', socket);
 };
 
 // There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to
 // the backing TCP or HTTPS server.
-// [1]: http://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
+// [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
 Server.prototype.listen = function listen(port, hostname) {
   this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) },
                  'Listening for incoming connections');
   this._server.listen.apply(this._server, arguments);
+
+  return this._server;
 };
 
 Server.prototype.close = function close(callback) {
   this._log.info('Closing server');
   this._server.close(callback);
 };
 
 Server.prototype.setTimeout = function setTimeout(timeout, callback) {
@@ -518,29 +564,33 @@ Object.defineProperty(Server.prototype, 
 });
 
 // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to
 // `server`.There are events on the `http.Server` class where it makes difference whether someone is
 // listening on the event or not. In these cases, we can not simply forward the events from the
 // `server` to `this` since that means a listener. Instead, we forward the subscriptions.
 Server.prototype.on = function on(event, listener) {
   if ((event === 'upgrade') || (event === 'timeout')) {
-    this._server.on(event, listener && listener.bind(this));
+    return this._server.on(event, listener && listener.bind(this));
   } else {
-    EventEmitter.prototype.on.call(this, event, listener);
+    return EventEmitter.prototype.on.call(this, event, listener);
   }
 };
 
 // `addContext` is used to add Server Name Indication contexts
 Server.prototype.addContext = function addContext(hostname, credentials) {
   if (this._mode === 'tls') {
     this._server.addContext(hostname, credentials);
   }
 };
 
+Server.prototype.address = function address() {
+  return this._server.address()
+};
+
 function createServerRaw(options, requestListener) {
   if (typeof options === 'function') {
     requestListener = options;
     options = {};
   }
 
   if (options.pfx || (options.key && options.cert)) {
     throw new Error('options.pfx, options.key, and options.cert are nonsensical!');
@@ -597,17 +647,17 @@ exports.http.createServer = exports.http
 // 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-16#section-8.1.2.3)
+// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3)
 // * `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.
@@ -616,16 +666,20 @@ IncomingRequest.prototype._onHeaders = f
   //   field MUST include '*'.
   // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A
   //   server MUST treat the absence of any of these header fields, presence of multiple values, or
   //   an invalid value as a stream error of type PROTOCOL_ERROR.
   this.method = this._checkSpecialHeader(':method'   , headers[':method']);
   this.scheme = this._checkSpecialHeader(':scheme'   , headers[':scheme']);
   this.host   = this._checkSpecialHeader(':authority', headers[':authority']  );
   this.url    = this._checkSpecialHeader(':path'     , headers[':path']  );
+  if (!this.method || !this.scheme || !this.host || !this.url) {
+    // This is invalid, and we've sent a RST_STREAM, so don't continue processing
+    return;
+  }
 
   // * Host header is included in the headers object for backwards compatibility.
   this.headers.host = this.host;
 
   // * Handling regular headers.
   IncomingMessage.prototype._onHeaders.call(this, headers);
 
   // * Signaling that the headers arrived.
@@ -679,22 +733,27 @@ OutgoingResponse.prototype.writeHead = f
 };
 
 OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() {
   if (!this.headersSent) {
     this.writeHead(this.statusCode);
   }
 };
 
+OutgoingResponse.prototype._implicitHeader = function() {
+  this._implicitHeaders();
+};
+
 OutgoingResponse.prototype.write = function write() {
   this._implicitHeaders();
   return OutgoingMessage.prototype.write.apply(this, arguments);
 };
 
 OutgoingResponse.prototype.end = function end() {
+  this.finshed = true;
   this._implicitHeaders();
   return OutgoingMessage.prototype.end.apply(this, arguments);
 };
 
 OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) {
   this._requestHeaders = headers;
 };
 
@@ -752,72 +811,92 @@ exports.globalAgent = undefined;
 function requestRaw(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
   options.plain = true;
   if (options.protocol && options.protocol !== "http:") {
     throw new Error('This interface only supports http-schemed URLs');
   }
-  return (options.agent || exports.globalAgent).request(options, callback);
+  if (options.agent && typeof(options.agent.request) === 'function') {
+    var agentOptions = util._extend({}, options);
+    delete agentOptions.agent;
+    return options.agent.request(agentOptions, callback);
+  }
+  return exports.globalAgent.request(options, callback);
 }
 
 function requestTLS(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
   options.plain = false;
   if (options.protocol && options.protocol !== "https:") {
     throw new Error('This interface only supports https-schemed URLs');
   }
-  return (options.agent || exports.globalAgent).request(options, callback);
+  if (options.agent && typeof(options.agent.request) === 'function') {
+    var agentOptions = util._extend({}, options);
+    delete agentOptions.agent;
+    return options.agent.request(agentOptions, callback);
+  }
+  return exports.globalAgent.request(options, callback);
 }
 
 function getRaw(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
   options.plain = true;
   if (options.protocol && options.protocol !== "http:") {
     throw new Error('This interface only supports http-schemed URLs');
   }
-  return (options.agent || exports.globalAgent).get(options, callback);
+  if (options.agent && typeof(options.agent.get) === 'function') {
+    var agentOptions = util._extend({}, options);
+    delete agentOptions.agent;
+    return options.agent.get(agentOptions, callback);
+  }
+  return exports.globalAgent.get(options, callback);
 }
 
 function getTLS(options, callback) {
   if (typeof options === "string") {
     options = url.parse(options);
   }
   options.plain = false;
   if (options.protocol && options.protocol !== "https:") {
     throw new Error('This interface only supports https-schemed URLs');
   }
-  return (options.agent || exports.globalAgent).get(options, callback);
+  if (options.agent && typeof(options.agent.get) === 'function') {
+    var agentOptions = util._extend({}, options);
+    delete agentOptions.agent;
+    return options.agent.get(agentOptions, callback);
+  }
+  return exports.globalAgent.get(options, callback);
 }
 
 // Agent class
 // -----------
 
 function Agent(options) {
   EventEmitter.call(this);
+  this.setMaxListeners(0);
 
   options = util._extend({}, options);
 
   this._settings = options.settings;
   this._log = (options.log || defaultLogger).child({ component: 'http' });
   this.endpoints = {};
 
   // * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when
   //   generating the key identifying the connection, so we may get useless non-negotiated TLS
   //   channels even if we ask for a negotiated one. This agent will contain only negotiated
   //   channels.
-  var agentOptions = {};
-  agentOptions.ALPNProtocols = supportedProtocols;
-  agentOptions.NPNProtocols = supportedProtocols;
-  this._httpsAgent = new https.Agent(agentOptions);
+  options.ALPNProtocols = supportedProtocols;
+  options.NPNProtocols = supportedProtocols;
+  this._httpsAgent = new https.Agent(options);
 
   this.sockets = this._httpsAgent.sockets;
   this.requests = this._httpsAgent.requests;
 }
 Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } });
 
 Agent.prototype.request = function request(options, callback) {
   if (typeof options === 'string') {
@@ -829,69 +908,92 @@ Agent.prototype.request = function reque
   options.method = (options.method || 'GET').toUpperCase();
   options.protocol = options.protocol || 'https:';
   options.host = options.hostname || options.host || 'localhost';
   options.port = options.port || 443;
   options.path = options.path || '/';
 
   if (!options.plain && options.protocol === 'http:') {
     this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1');
-    throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.');
+    this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.'));
   }
 
   var request = new OutgoingRequest(this._log);
 
   if (callback) {
     request.on('response', callback);
   }
 
   var key = [
     !!options.plain,
     options.host,
     options.port
   ].join(':');
+  var self = this;
 
   // * There's an existing HTTP/2 connection to this host
   if (key in this.endpoints) {
     var endpoint = this.endpoints[key];
     request._start(endpoint.createStream(), options);
   }
 
   // * HTTP/2 over plain TCP
   else if (options.plain) {
     endpoint = new Endpoint(this._log, 'CLIENT', this._settings);
     endpoint.socket = net.connect({
       host: options.host,
       port: options.port,
       localAddress: options.localAddress
     });
+
+    endpoint.socket.on('error', function (error) {
+      self._log.error('Socket error: ' + error.toString());
+      request.emit('error', error);
+    });
+
+    endpoint.on('error', function(error){
+      self._log.error('Connection error: ' + error.toString());
+      request.emit('error', error);
+    });
+
+    this.endpoints[key] = endpoint;
     endpoint.pipe(endpoint.socket).pipe(endpoint);
     request._start(endpoint.createStream(), options);
   }
 
   // * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1
   else {
     var started = false;
+    var createAgent = hasAgentOptions(options);
     options.ALPNProtocols = supportedProtocols;
     options.NPNProtocols = supportedProtocols;
     options.servername = options.host; // Server Name Indication
-    options.agent = this._httpsAgent;
     options.ciphers = options.ciphers || cipherSuites;
+    if (createAgent) {
+      options.agent = new https.Agent(options);
+    } else if (options.agent == null) {
+      options.agent = this._httpsAgent;
+    }
     var httpsRequest = https.request(options);
 
+    httpsRequest.on('error', function (error) {
+      self._log.error('Socket error: ' + error.toString());
+      self.removeAllListeners(key);
+      request.emit('error', error);
+    });
+
     httpsRequest.on('socket', function(socket) {
       var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
       if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0
         negotiated();
       } else {
         socket.on('secureConnect', negotiated);
       }
     });
 
-    var self = this;
     function negotiated() {
       var endpoint;
       var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol;
       if (negotiatedProtocol === protocol.VERSION) {
         httpsRequest.socket.emit('agentRemove');
         unbundleSocket(httpsRequest.socket);
         endpoint = new Endpoint(self._log, 'CLIENT', self._settings);
         endpoint.socket = httpsRequest.socket;
@@ -930,27 +1032,47 @@ Agent.prototype.request = function reque
 };
 
 Agent.prototype.get = function get(options, callback) {
   var request = this.request(options, callback);
   request.end();
   return request;
 };
 
+Agent.prototype.destroy = function(error) {
+  if (this._httpsAgent) {
+    this._httpsAgent.destroy();
+  }
+  for (var key in this.endpoints) {
+    this.endpoints[key].close(error);
+  }
+};
+
 function unbundleSocket(socket) {
   socket.removeAllListeners('data');
   socket.removeAllListeners('end');
   socket.removeAllListeners('readable');
   socket.removeAllListeners('close');
   socket.removeAllListeners('error');
   socket.unpipe();
   delete socket.ondata;
   delete socket.onend;
 }
 
+function hasAgentOptions(options) {
+  return options.pfx != null ||
+    options.key != null ||
+    options.passphrase != null ||
+    options.cert != null ||
+    options.ca != null ||
+    options.ciphers != null ||
+    options.rejectUnauthorized != null ||
+    options.secureProtocol != null;
+}
+
 Object.defineProperty(Agent.prototype, 'maxSockets', {
   get: function getMaxSockets() {
     return this._httpsAgent.maxSockets;
   },
   set: function setMaxSockets(value) {
     this._httpsAgent.maxSockets = value;
   }
 });
@@ -966,16 +1088,17 @@ function OutgoingRequest() {
   this._log = undefined;
 
   this.stream = undefined;
 }
 OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } });
 
 OutgoingRequest.prototype._start = function _start(stream, options) {
   this.stream = stream;
+  this.options = options;
 
   this._log = stream._log.child({ component: 'http' });
 
   for (var key in options.headers) {
     this.setHeader(key, options.headers[key]);
   }
   var headers = this._headers;
   delete headers.host;
@@ -991,18 +1114,18 @@ OutgoingRequest.prototype._start = funct
 
   this._log.info({ scheme: headers[':scheme'], method: headers[':method'],
                    authority: headers[':authority'], path: headers[':path'],
                    headers: (options.headers || {}) }, 'Sending request');
   this.stream.headers(headers);
   this.headersSent = true;
 
   this.emit('socket', this.stream);
-
   var response = new IncomingResponse(this.stream);
+  response.req = this;
   response.once('ready', this.emit.bind(this, 'response', response));
 
   this.stream.on('promise', this._onPromise.bind(this));
 };
 
 OutgoingRequest.prototype._fallback = function _fallback(request) {
   request.on('response', this.emit.bind(this, 'response'));
   this.stream = this.request = request;
@@ -1079,17 +1202,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-16#section-8.1.2.4)
+// [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4)
 // * `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,24 +1,24 @@
-// [node-http2][homepage] is an [HTTP/2 (draft 16)][http2] implementation for [node.js][node].
+// [node-http2][homepage] is an [HTTP/2][http2] implementation for [node.js][node].
 //
 // The core of the protocol is implemented in the protocol sub-directory. This directory provides
 // two important features on top of the 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]:               http://tools.ietf.org/html/draft-ietf-httpbis-http2-16
-// [node]:                http://nodejs.org/
-// [node-https]:          http://nodejs.org/api/https.html
-// [node-http]:           http://nodejs.org/api/http.html
+// [http2]:               https://tools.ietf.org/html/rfc7540
+// [node]:                https://nodejs.org/
+// [node-https]:          https://nodejs.org/api/https.html
+// [node-http]:           https://nodejs.org/api/http.html
 
 module.exports   = require('./http');
 
 /*
                   HTTP API
 
                |            ^
                |            |
--- a/testing/xpcshell/node-http2/lib/protocol/compressor.js
+++ b/testing/xpcshell/node-http2/lib/protocol/compressor.js
@@ -6,19 +6,19 @@
 // These transform chunks of binary data into `[name, value]` pairs and vice versa, and store their
 // state in `HeaderTable` instances.
 //
 // The 'integration' part is also implemented by two [Transform Stream][node-transform] subclasses
 // that operate in [object mode][node-objectmode]: the `Compressor` and the `Decompressor`. These
 // provide a layer between the [framer](framer.html) and the
 // [connection handling component](connection.html).
 //
-// [node-transform]: http://nodejs.org/api/stream.html#stream_class_stream_transform
-// [node-objectmode]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
-// [http2-compression]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07
+// [node-transform]: https://nodejs.org/api/stream.html#stream_class_stream_transform
+// [node-objectmode]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options
+// [http2-compression]: https://tools.ietf.org/html/rfc7541
 
 exports.HeaderTable = HeaderTable;
 exports.HuffmanTable = HuffmanTable;
 exports.HeaderSetCompressor = HeaderSetCompressor;
 exports.HeaderSetDecompressor = HeaderSetDecompressor;
 exports.Compressor = Compressor;
 exports.Decompressor = Decompressor;
 
@@ -30,18 +30,18 @@ var util = require('util');
 // ==================
 
 // The HeaderTable class
 // ---------------------
 
 // The [Header Table] is a component used to associate headers to index values. It is basically an
 // ordered list of `[name, value]` pairs, so it's implemented as a subclass of `Array`.
 // In this implementation, the Header Table and the [Static Table] are handled as a single table.
-// [Header Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.2
-// [Static Table]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
+// [Header Table]: https://tools.ietf.org/html/rfc7541#section-2.3.2
+// [Static Table]: https://tools.ietf.org/html/rfc7541#section-2.3.1
 function HeaderTable(log, limit) {
   var self = HeaderTable.staticTable.map(entryFromPair);
   self._log = log;
   self._limit = limit || DEFAULT_HEADER_TABLE_LIMIT;
   self._staticLength = self.length;
   self._size = 0;
   self._enforceLimit = HeaderTable.prototype._enforceLimit;
   self.add = HeaderTable.prototype.add;
@@ -65,17 +65,17 @@ function entryFromPair(pair) {
 // * The size of a header table is the sum of the size of its entries.
 var DEFAULT_HEADER_TABLE_LIMIT = 4096;
 
 function size(entry) {
   return (new Buffer(entry[0] + entry[1], 'utf8')).length + 32;
 }
 
 // The `add(index, entry)` can be used to [manage the header table][tablemgmt]:
-// [tablemgmt]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.3
+// [tablemgmt]: https://tools.ietf.org/html/rfc7541#section-4
 //
 // * 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 ---------->
@@ -110,19 +110,18 @@ HeaderTable.prototype.add = function(ent
 };
 
 // The table size limit can be changed externally. In this case, the same eviction algorithm is used
 HeaderTable.prototype.setSizeLimit = function setSizeLimit(limit) {
   this._limit = limit;
   this._enforceLimit(this._limit);
 };
 
-// [The Static Table](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B)
+// [The Static Table](https://tools.ietf.org/html/rfc7541#section-2.3.1)
 // ------------------
-// [statictable]:http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
 
 // The table is generated with feeding the table from the spec to the following sed command:
 //
 //     sed -re "s/\s*\| [0-9]+\s*\| ([^ ]*)/  [ '\1'/g" -e "s/\|\s([^ ]*)/, '\1'/g" -e 's/ \|/],/g'
 
 HeaderTable.staticTable  = [
   [ ':authority'                  , ''            ],
   [ ':method'                     , 'GET'         ],
@@ -203,24 +202,24 @@ function HeaderSetDecompressor(log, tabl
 
   this._log = log.child({ component: 'compressor' });
   this._table = table;
   this._chunks = [];
 }
 
 // `_transform` is the implementation of the [corresponding virtual function][_transform] of the
 // TransformStream class. It collects the data chunks for later processing.
-// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
+// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
 HeaderSetDecompressor.prototype._transform = function _transform(chunk, encoding, callback) {
   this._chunks.push(chunk);
   callback();
 };
 
 // `execute(rep)` executes the given [header representation][representation].
-// [representation]: http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-3.1.4
+// [representation]: https://tools.ietf.org/html/rfc7541#section-6
 
 // The *JavaScript object representation* of a header representation:
 //
 //     {
 //       name: String || Integer,  // string literal or index
 //       value: String || Integer, // string literal or index
 //       index: Boolean            // with or without indexing
 //     }
@@ -237,17 +236,17 @@ HeaderSetDecompressor.prototype._transfo
 //     { 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) {
-    this.setTableSizeLimit(rep.newMaxSize);
+    this._table.setSizeLimit(rep.newMaxSize);
   }
 
   // * 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];
 
@@ -276,17 +275,17 @@ HeaderSetDecompressor.prototype._execute
 
     this.push(pair);
   }
 };
 
 // `_flush` is the implementation of the [corresponding virtual function][_flush] of the
 // TransformStream class. The whole decompressing process is done in `_flush`. It gets called when
 // the input stream is over.
-// [_flush]: http://nodejs.org/api/stream.html#stream_transform_flush_callback
+// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback
 HeaderSetDecompressor.prototype._flush = function _flush(callback) {
   var buffer = concat(this._chunks);
 
   // * processes the header representations
   buffer.cursor = 0;
   while (buffer.cursor < buffer.length) {
     this._execute(HeaderSetDecompressor.header(buffer));
   }
@@ -322,17 +321,17 @@ HeaderSetCompressor.prototype.send = fun
   if (!rep.chunks) {
     rep.chunks = HeaderSetCompressor.header(rep);
   }
   rep.chunks.forEach(this.push);
 };
 
 // `_transform` is the implementation of the [corresponding virtual function][_transform] of the
 // TransformStream class. It processes the input headers one by one:
-// [_transform]: http://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
+// [_transform]: https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
 HeaderSetCompressor.prototype._transform = function _transform(pair, encoding, callback) {
   var name = pair[0].toLowerCase();
   var value = pair[1];
   var entry, rep;
 
   // * tries to find full (name, value) or name match in the header table
   var nameMatch = -1, fullMatch = -1;
   for (var droppedIndex = 0; droppedIndex < this._table.length; droppedIndex++) {
@@ -368,22 +367,22 @@ HeaderSetCompressor.prototype._transform
     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
+// [_flush]: https://nodejs.org/api/stream.html#stream_transform_flush_callback
 HeaderSetCompressor.prototype._flush = function _flush(callback) {
   callback();
 };
 
-// [Detailed Format](http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#section-4)
+// [Detailed Format](https://tools.ietf.org/html/rfc7541#section-5)
 // -----------------
 
 // ### Integer representation ###
 //
 // The algorithm to represent an integer I is as follows:
 //
 // 1. If I < 2^N - 1, encode I on N bits
 // 2. Else, encode 2^N - 1 on N bits and do the following steps:
@@ -1086,28 +1085,47 @@ var MAX_HTTP_PAYLOAD_SIZE = 16384;
 util.inherits(Compressor, TransformStream);
 function Compressor(log, type) {
   TransformStream.call(this, { objectMode: true });
 
   this._log = log.child({ component: 'compressor' });
 
   assert((type === 'REQUEST') || (type === 'RESPONSE'));
   this._table = new HeaderTable(this._log);
+
+  this.tableSizeChangePending = false;
+  this.lowestTableSizePending = 0;
+  this.tableSizeSetting = DEFAULT_HEADER_TABLE_LIMIT;
 }
 
 // Changing the header table size
 Compressor.prototype.setTableSizeLimit = function setTableSizeLimit(size) {
   this._table.setSizeLimit(size);
+  if (!this.tableSizeChangePending || size < this.lowestTableSizePending) {
+    this.lowestTableSizePending = size;
+  }
+  this.tableSizeSetting = size;
+  this.tableSizeChangePending = true;
 };
 
 // `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);
+
+  if (this.tableSizeChangePending) {
+    if (this.lowestTableSizePending < this.tableSizeSetting) {
+      compressor.send({contextUpdate: true, newMaxSize: this.lowestTableSizePending,
+                       name: "", value: "", index: 0});
+    }
+    compressor.send({contextUpdate: true, newMaxSize: this.tableSizeSetting,
+                     name: "", value: "", index: 0});
+    this.tableSizeChangePending = false;
+  }
   var colonHeaders = [];
   var nonColonHeaders = [];
 
   // To ensure we send colon headers first
   for (var name in headers) {
     if (name.trim()[0] === ':') {
       colonHeaders.push(name);
     } else {
--- a/testing/xpcshell/node-http2/lib/protocol/connection.js
+++ b/testing/xpcshell/node-http2/lib/protocol/connection.js
@@ -348,16 +348,37 @@ Connection.prototype._receive = function
   this._log.trace({ frame: frame }, 'Forwarding incoming frame');
 
   // * first frame needs to be checked by the `_onFirstFrameReceived` method
   if (!this._firstFrameReceived) {
     this._firstFrameReceived = true;
     this._onFirstFrameReceived(frame);
   }
 
+  // Do some sanity checking here before we create a stream
+  if ((frame.type == 'SETTINGS' ||
+       frame.type == 'PING' ||
+       frame.type == 'GOAWAY') &&
+      frame.stream != 0) {
+    // Got connection-level frame on a stream - EEP!
+    this.close('PROTOCOL_ERROR');
+    return;
+  } else if ((frame.type == 'DATA' ||
+              frame.type == 'HEADERS' ||
+              frame.type == 'PRIORITY' ||
+              frame.type == 'RST_STREAM' ||
+              frame.type == 'PUSH_PROMISE' ||
+              frame.type == 'CONTINUATION') &&
+             frame.stream == 0) {
+    // Got stream-level frame on connection - EEP!
+    this.close('PROTOCOL_ERROR');
+    return;
+  }
+  // WINDOW_UPDATE can be on either stream or connection
+
   // * gets the appropriate stream from the stream registry
   var stream = this._streamIds[frame.stream];
 
   // * or creates one if it's not in `this.streams`
   if (!stream) {
     stream = this._createIncomingStream(frame.stream);
   }
 
@@ -396,17 +417,17 @@ Connection.prototype._initializeSettings
 };
 
 // * 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.');
-    this.emit('error');
+    this.emit('error', 'PROTOCOL_ERROR');
   }
 };
 
 // Handling of incoming SETTINGS frames.
 Connection.prototype._receiveSettings = function _receiveSettings(frame) {
   // * If it's an ACK, call the appropriate callback
   if (frame.flags.ACK) {
     var callback = this._settingsAckCallbacks.shift();
--- a/testing/xpcshell/node-http2/lib/protocol/flow.js
+++ b/testing/xpcshell/node-http2/lib/protocol/flow.js
@@ -1,30 +1,30 @@
 var assert = require('assert');
 
 // The Flow class
 // ==============
 
 // Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
 // subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
-// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
+// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex
 
 var Duplex  = require('stream').Duplex;
 
 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-16#section-6.9.2
+// [1]: https://tools.ietf.org/html/rfc7540#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
@@ -37,17 +37,17 @@ exports.Flow = Flow;
 //
 //   Returns `true` if it needs more frames in the output queue, `false` if the output queue is
 //   full, and `null` if did not push the frame into the output queue (instead, it pushed it into
 //   the flow control queue).
 //
 // * **read(limit): frame**: like the regular `read`, but the 'flow control size' (0 for non-DATA
 //   frames, length of the payload for DATA frames) of the returned frame will be under `limit`.
 //   Small exception: pass -1 as `limit` if the max. flow control size is 0. `read(0)` means the
-//   same thing as [in the original API](http://nodejs.org/api/stream.html#stream_stream_read_0).
+//   same thing as [in the original API](https://nodejs.org/api/stream.html#stream_stream_read_0).
 //
 // * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
 //
 // * **_log**: the Flow class uses the `_log` object of the parent
 
 // Constructor
 // -----------
 
@@ -74,17 +74,17 @@ Flow.prototype = Object.create(Duplex.pr
 // `_receive` is called when there's an incoming frame.
 Flow.prototype._receive = function _receive(frame, callback) {
   throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
 };
 
 // `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
 // to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
 // incoming frame is a WINDOW_UPDATE.
-// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
+// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
 Flow.prototype._write = function _write(frame, encoding, callback) {
   var sentToUs = (this._flowControlId === undefined) || (frame.stream === this._flowControlId);
 
   if (sentToUs && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) {
     this._ended = true;
   }
 
   if ((frame.type === 'DATA') && (frame.data.length > 0)) {
@@ -142,17 +142,17 @@ Flow.prototype._restoreWindow = function
 
 // `_send` is called when more frames should be pushed to the output buffer.
 Flow.prototype._send = function _send() {
   throw new Error('The _send() method has to be overridden by the child class!');
 };
 
 // `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
 // items in the output queue.
-// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
+// [1]: https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
 Flow.prototype._read = function _read() {
   // * if the flow control queue is empty, then let the user push more frames
   if (this._queue.length === 0) {
     this._send();
   }
 
   // * if there are items in the flow control queue, then let's put them into the output queue (to
   //   the extent it is possible with respect to the window size and output queue feedback)
@@ -162,17 +162,17 @@ Flow.prototype._read = function _read() 
     do {
       var moreNeeded = this._push(this._queue[0]);
       if (moreNeeded !== null) {
         this._queue.shift();
       }
     } while (moreNeeded && (this._queue.length > 0));
     this._readableState.sync = false;
 
-    assert((moreNeeded == false) ||                              // * output queue is full
+    assert((!moreNeeded) ||                              // * output queue is full
            (this._queue.length === 0) ||                         // * flow control queue is empty
            (!this._window && (this._queue[0].type === 'DATA'))); // * waiting for window update
   }
 
   // * otherwise, come back when the flow control window is positive
   else if (!this._blocked) {
     this._parentPush({
       type: 'BLOCKED',
@@ -244,34 +244,35 @@ Flow.prototype._parentPush = function _p
 };
 
 // `_push(frame)` pushes `frame` into the output queue and decreases the flow control window size.
 // It is capable of splitting DATA frames into smaller parts, if the window size is not enough to
 // push the whole frame. The return value is similar to `push` except that it returns `null` if it
 // did not push the whole frame to the output queue (but maybe it did push part of the frame).
 Flow.prototype._push = function _push(frame) {
   var data = frame && (frame.type === 'DATA') && frame.data;
+  var maxFrameLength = (this._window < 16384) ? this._window : 16384;
 
-  if (!data || (data.length <= this._window)) {
+  if (!data || (data.length <= maxFrameLength)) {
     return this._parentPush(frame);
   }
 
   else if (this._window <= 0) {
     return null;
   }
 
   else {
     this._log.trace({ frame: frame, size: frame.data.length, forwardable: this._window },
                     'Splitting out forwardable part of a DATA frame.');
-    frame.data = data.slice(this._window);
+    frame.data = data.slice(maxFrameLength);
     this._parentPush({
       type: 'DATA',
       flags: {},
       stream: frame.stream,
-      data: data.slice(0, this._window)
+      data: data.slice(0, maxFrameLength)
     });
     return null;
   }
 };
 
 // Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
 Flow.prototype.push = function push(frame) {
   if (frame === null) {
@@ -318,17 +319,19 @@ Flow.prototype._increaseWindow = functio
     this.emit('error', 'FLOW_CONTROL_ERROR');
   } else {
     this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
     this._window += size;
     if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
       this._log.error('Flow control window grew too large.');
       this.emit('error', 'FLOW_CONTROL_ERROR');
     } else {
-      this.emit('window_update');
+      if (size != 0) {
+        this.emit('window_update');
+      }
     }
   }
 };
 
 // The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
 // modifies the flow control window:
 //
 // * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -1,12 +1,12 @@
 // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]:
 // the Serializer and the Deserializer
-// [1]: http://nodejs.org/api/stream.html#stream_class_stream_transform
-// [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
+// [1]: https://nodejs.org/api/stream.html#stream_class_stream_transform
+// [2]: https://nodejs.org/api/stream.html#stream_new_stream_readable_options
 var assert = require('assert');
 
 var Transform = require('stream').Transform;
 
 exports.Serializer = Serializer;
 exports.Deserializer = Deserializer;
 
 var logData = Boolean(process.env.HTTP2_LOG_DATA);
@@ -141,20 +141,20 @@ Deserializer.prototype._transform = func
       }
       this._next(COMMON_HEADER_SIZE);
     }
   }
 
   done();
 };
 
-// [Frame Header](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1)
+// [Frame Header](https://tools.ietf.org/html/rfc7540#section-4.1)
 // --------------------------------------------------------------
 //
-// HTTP/2.0 frames share a common base format consisting of a 9-byte header followed by 0 to 2^24 - 1
+// HTTP/2 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,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
 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -230,16 +230,20 @@ Serializer.commonHeader = function write
   headerBuffer.writeUInt32BE(frame.stream || 0, 5);
 
   buffers.unshift(headerBuffer);
 
   return size;
 };
 
 Deserializer.commonHeader = function readCommonHeader(buffer, frame) {
+  if (buffer.length < 9) {
+    return 'FRAME_SIZE_ERROR';
+  }
+
   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(3)];
   if (!frame.type) {
@@ -264,17 +268,17 @@ Deserializer.commonHeader = function rea
 
 // 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-16#section-6.1)
+// [DATA Frames](https://tools.ietf.org/html/rfc7540#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):
@@ -292,28 +296,36 @@ typeSpecificAttributes.DATA = ['data'];
 Serializer.DATA = function writeData(frame, buffers) {
   buffers.push(frame.data);
 };
 
 Deserializer.DATA = function readData(buffer, frame) {
   var dataOffset = 0;
   var paddingLength = 0;
   if (frame.flags.PADDED) {
+    if (buffer.length < 1) {
+      // We must have at least one byte for padding control, but we don't. Bad peer!
+      return 'FRAME_SIZE_ERROR';
+    }
     paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
     dataOffset = 1;
   }
 
   if (paddingLength) {
+    if (paddingLength >= (buffer.length - 1)) {
+      // We don't have enough room for the padding advertised - bad peer!
+      return 'FRAME_SIZE_ERROR';
+    }
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [HEADERS](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.2)
+// [HEADERS](https://tools.ietf.org/html/rfc7540#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
@@ -360,16 +372,28 @@ Serializer.HEADERS = function writeHeade
     assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight);
     buffer.writeUInt8(frame.priorityWeight, 4);
     buffers.push(buffer);
   }
   buffers.push(frame.data);
 };
 
 Deserializer.HEADERS = function readHeadersPriority(buffer, frame) {
+  var minFrameLength = 0;
+  if (frame.flags.PADDED) {
+    minFrameLength += 1;
+  }
+  if (frame.flags.PRIORITY) {
+    minFrameLength += 5;
+  }
+  if (buffer.length < minFrameLength) {
+    // Peer didn't send enough data - bad peer!
+    return 'FRAME_SIZE_ERROR';
+  }
+
   var dataOffset = 0;
   var paddingLength = 0;
   if (frame.flags.PADDED) {
     paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
     dataOffset = 1;
   }
 
   if (frame.flags.PRIORITY) {
@@ -379,23 +403,27 @@ Deserializer.HEADERS = function readHead
     frame.exclusiveDependency = !!(dependencyData[0] & 0x80);
     dependencyData[0] &= 0x7f;
     frame.priorityDependency = dependencyData.readUInt32BE(0);
     frame.priorityWeight = buffer.readUInt8(dataOffset);
     dataOffset += 1;
   }
 
   if (paddingLength) {
+    if ((buffer.length - dataOffset) < paddingLength) {
+      // Not enough data left to satisfy the advertised padding - bad peer!
+      return 'FRAME_SIZE_ERROR';
+    }
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PRIORITY](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.3)
+// [PRIORITY](https://tools.ietf.org/html/rfc7540#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';
 
@@ -422,25 +450,29 @@ Serializer.PRIORITY = function writePrio
   }
   assert((0 <= frame.priorityWeight) && (frame.priorityWeight <= 0xff), frame.priorityWeight);
   buffer.writeUInt8(frame.priorityWeight, 4);
 
   buffers.push(buffer);
 };
 
 Deserializer.PRIORITY = function readPriority(buffer, frame) {
+  if (buffer.length < 5) {
+    // PRIORITY frames are 5 bytes long. Bad peer!
+    return 'FRAME_SIZE_ERROR';
+  }
   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-16#section-6.4)
+// [RST_STREAM](https://tools.ietf.org/html/rfc7540#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';
 
@@ -461,24 +493,28 @@ Serializer.RST_STREAM = function writeRs
   var buffer = new Buffer(4);
   var code = errorCodes.indexOf(frame.error);
   assert((0 <= code) && (code <= 0xffffffff), code);
   buffer.writeUInt32BE(code, 0);
   buffers.push(buffer);
 };
 
 Deserializer.RST_STREAM = function readRstStream(buffer, frame) {
+  if (buffer.length < 4) {
+    // RST_STREAM is 4 bytes long. Bad peer!
+    return 'FRAME_SIZE_ERROR';
+  }
   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-16#section-6.5)
+// [SETTINGS](https://tools.ietf.org/html/rfc7540#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):
@@ -575,17 +611,17 @@ definedSettings[3] = { name: 'SETTINGS_M
 // * 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 };
 
 // * 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-16#section-6.6)
+// [PUSH_PROMISE](https://tools.ietf.org/html/rfc7540#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):
@@ -621,32 +657,41 @@ Serializer.PUSH_PROMISE = function write
   assert((0 <= promised_stream) && (promised_stream <= 0x7fffffff), promised_stream);
   buffer.writeUInt32BE(promised_stream, 0);
 
   buffers.push(buffer);
   buffers.push(frame.data);
 };
 
 Deserializer.PUSH_PROMISE = function readPushPromise(buffer, frame) {
+  if (buffer.length < 4) {
+    return 'FRAME_SIZE_ERROR';
+  }
   var dataOffset = 0;
   var paddingLength = 0;
   if (frame.flags.PADDED) {
+    if (buffer.length < 5) {
+      return 'FRAME_SIZE_ERROR';
+    }
     paddingLength = (buffer.readUInt8(dataOffset) & 0xff);
     dataOffset = 1;
   }
   frame.promised_stream = buffer.readUInt32BE(dataOffset) & 0x7fffffff;
   dataOffset += 4;
   if (paddingLength) {
+    if ((buffer.length - dataOffset) < paddingLength) {
+      return 'FRAME_SIZE_ERROR';
+    }
     frame.data = buffer.slice(dataOffset, -1 * paddingLength);
   } else {
     frame.data = buffer.slice(dataOffset);
   }
 };
 
-// [PING](http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.7)
+// [PING](https://tools.ietf.org/html/rfc7540#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):
@@ -666,17 +711,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-16#section-6.8)
+// [GOAWAY](https://tools.ietf.org/html/rfc7540#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';
 
@@ -709,25 +754,29 @@ Serializer.GOAWAY = function writeGoaway
   var code = errorCodes.indexOf(frame.error);
   assert((0 <= code) && (code <= 0xffffffff), code);
   buffer.writeUInt32BE(code, 4);
 
   buffers.push(buffer);
 };
 
 Deserializer.GOAWAY = function readGoaway(buffer, frame) {
+  if (buffer.length !== 8) {
+    // GOAWAY must have 8 bytes
+    return 'FRAME_SIZE_ERROR';
+  }
   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-16#section-6.9)
+// [WINDOW_UPDATE](https://tools.ietf.org/html/rfc7540#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';
 
@@ -755,17 +804,17 @@ Deserializer.WINDOW_UPDATE = function re
     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-16#section-6.10)
+// [CONTINUATION](https://tools.ietf.org/html/rfc7540#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
@@ -780,17 +829,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-alt-svc-06#section-4)
+// [ALTSVC](https://tools.ietf.org/html/rfc7838#section-4)
 // ------------------------------------------------------------
 //
 // 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';
 
@@ -805,23 +854,23 @@ frameFlags.ALTSVC = [];
 //    +---------------------------------------------------------------+
 //
 // The ALTSVC frame contains the following fields:
 //
 // Origin-Len: An unsigned, 16-bit integer indicating the length, in
 //    octets, of the Origin field.
 //
 // Origin: An OPTIONAL sequence of characters containing ASCII
-//    serialisation of an origin ([RFC6454](http://tools.ietf.org/html/rfc6454),
+//    serialisation of an origin ([RFC6454](https://tools.ietf.org/html/rfc6454),
 //    Section 6.2) that the alternate service is applicable to.
 //
 // Alt-Svc-Field-Value: A sequence of octets (length determined by
 //    subtracting the length of all preceding fields from the frame
 //    length) containing a value identical to the Alt-Svc field value
-//    defined in (Section 3)[http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-3]
+//    defined in (Section 3)[https://tools.ietf.org/html/rfc7838#section-3]
 //    (ABNF production "Alt-Svc").
 
 typeSpecificAttributes.ALTSVC = ['maxAge', 'port', 'protocolID', 'host',
                                  'origin'];
 
 function istchar(c) {
   return ('!#$&\'*+-.^_`|~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.indexOf(c) > -1);
 }
@@ -981,17 +1030,23 @@ function unescape(s) {
     }
 
     ++i;
   }
   return t;
 }
 
 Deserializer.ALTSVC = function readAltSvc(buffer, frame) {
+  if (buffer.length < 2) {
+    return 'FRAME_SIZE_ERROR';
+  }
   var originLength = buffer.readUInt16BE(0);
+  if ((buffer.length - 2) < originLength) {
+    return 'FRAME_SIZE_ERROR';
+  }
   frame.origin = buffer.toString('ascii', 2, 2 + originLength);
   var fieldValue = buffer.toString('ascii', 2 + originLength);
   var values = parseHeaderValue(fieldValue, ',', splitHeaderParameters);
   if (values.length > 1) {
     // TODO - warn that we only use one here
   }
   if (values.length === 0) {
     // Well that's a malformed frame. Just ignore it.
@@ -1029,17 +1084,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-16#section-7)
+// [Error Codes](https://tools.ietf.org/html/rfc7540#section-7)
 // ------------------------------------------------------------
 
 var errorCodes = [
   'NO_ERROR',
   'PROTOCOL_ERROR',
   'INTERNAL_ERROR',
   'FLOW_CONTROL_ERROR',
   'SETTINGS_TIMEOUT',
--- a/testing/xpcshell/node-http2/lib/protocol/index.js
+++ b/testing/xpcshell/node-http2/lib/protocol/index.js
@@ -1,46 +1,45 @@
-// [node-http2-protocol][homepage] is an implementation of the [HTTP/2 (draft 16)][http2]
+// This is an implementation of the [HTTP/2][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
 //   [connection header][http2-connheader]) and manages other components (framer, compressor,
 //   connection, streams) that make up a client or server.
 //
 // * [Connection](connection.html): multiplexes the active HTTP/2 streams, manages connection
 //   lifecycle and settings, and responsible for enforcing the connection level limits (flow
 //   control, initiated stream limit)
 //
-// * [Stream](stream.html): implementation of the [HTTP/2 stream concept](http2-stream).
+// * [Stream](stream.html): implementation of the [HTTP/2 stream concept][http2-stream].
 //   Implements the [stream state machine][http2-streamstate] defined by the standard, provides
 //   management methods and events for using the stream (sending/receiving headers, data, etc.),
 //   and enforces stream level constraints (flow control, sending only legal frames).
 //
 // * [Flow](flow.html): implements flow control for Connection and Stream as parent class.
 //
 // * [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-16
-// [http2-connheader]:    http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-3.5
-// [http2-stream]:        http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5
-// [http2-streamstate]:   http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#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
+// [http2]:               https://tools.ietf.org/html/rfc7540
+// [http2-connheader]:    https://tools.ietf.org/html/rfc7540#section-3.5
+// [http2-stream]:        https://tools.ietf.org/html/rfc7540#section-5
+// [http2-streamstate]:   https://tools.ietf.org/html/rfc7540#section-5.1
+// [node]:                https://nodejs.org/
+// [node-stream]:         https://nodejs.org/api/stream.html
+// [node-https]:          https://nodejs.org/api/https.html
+// [node-http]:           https://nodejs.org/api/http.html
 
 exports.VERSION = 'h2';
 
 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'];
--- a/testing/xpcshell/node-http2/lib/protocol/stream.js
+++ b/testing/xpcshell/node-http2/lib/protocol/stream.js
@@ -1,15 +1,15 @@
 var assert = require('assert');
 
 // The Stream class
 // ================
 
-// Stream is a [Duplex stream](http://nodejs.org/api/stream.html#stream_class_stream_duplex)
-// subclass that implements the [HTTP/2 Stream](http://http2.github.io/http2-spec/#rfc.section.3.4)
+// Stream is a [Duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex)
+// subclass that implements the [HTTP/2 Stream](https://tools.ietf.org/html/rfc7540#section-5)
 // concept. It has two 'sides': one that is used by the user to send/receive data (the `stream`
 // object itself) and one that is used by a Connection to read/write frames to/from the other peer
 // (`stream.upstream`).
 
 var Duplex = require('stream').Duplex;
 
 exports.Stream = Stream;
 
@@ -35,17 +35,17 @@ exports.Stream = Stream;
 //   too, but once it is set locally, it can not be changed remotely.
 //
 // * **reset(error)**: reset the stream with an error code
 //
 // * **upstream**: a [Flow](flow.js) that is used by the parent connection to write/read frames
 //   that are to be sent/arrived to/from the peer and are related to this stream.
 //
 // Headers are always in the [regular node.js header format][1].
-// [1]: http://nodejs.org/api/http.html#http_message_headers
+// [1]: https://nodejs.org/api/http.html#http_message_headers
 
 // Constructor
 // -----------
 
 // The main aspects of managing the stream are:
 function Stream(log, connection) {
   Duplex.call(this);
 
@@ -177,17 +177,17 @@ Stream.prototype.altsvc = function altsv
 
 // Data flow
 // ---------
 
 // The incoming and the generated outgoing frames are received/transmitted on the `this.upstream`
 // [Flow](flow.html). The [Connection](connection.html) object instantiating the stream will read
 // and write frames to/from it. The stream itself is a regular [Duplex stream][1], and is used by
 // the user to write or read the body of the request.
-// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
+// [1]: https://nodejs.org/api/stream.html#stream_class_stream_duplex
 
 //     upstream side                  stream                  user side
 //
 //                    +------------------------------------+
 //                    |                                    |
 //                    +------------------+                 |
 //                    |     upstream     |                 |
 //                    |                  |                 |
@@ -347,17 +347,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-16#section-5.1)
+// [Stream States](https://tools.ietf.org/html/rfc7540#section-5.1)
 // ----------------
 //
 //                           +--------+
 //                     PP    |        |    PP
 //                  ,--------|  idle  |--------.
 //                 /         |        |         \
 //                v          +--------+          v
 //         +----------+          |           +----------+
@@ -562,18 +562,19 @@ Stream.prototype._transition = function 
     //   it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
     //   to limit the period over which it ignores frames and treat frames that arrive after this
     //   time as being in error.
     // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
     //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
     //   can be used to close any of those streams.
     case 'CLOSED':
       if (PRIORITY || (sending && RST_STREAM) ||
+          (receiving && WINDOW_UPDATE) ||
           (receiving && this._closedByUs &&
-           (this._closedWithRst || WINDOW_UPDATE || RST_STREAM || ALTSVC))) {
+           (this._closedWithRst || RST_STREAM || ALTSVC))) {
         /* No state change */
       } else {
         streamError = 'STREAM_CLOSED';
       }
       break;
   }
 
   // Noting that the connection was closed by the other endpoint. It may be important in edge cases.
@@ -619,17 +620,17 @@ Stream.prototype._transition = function 
       state: this.state,
       closedByUs: this._closedByUs,
       closedWithRst: this._closedWithRst
     };
 
     // * When sending something invalid, throwing an exception, since it is probably a bug.
     if (sending) {
       this._log.error(info, 'Sending illegal frame.');
-      throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
+      return this.emit('error', new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.'));
     }
 
     // * In case of a serious problem, emitting and error and letting someone else handle it
     //   (e.g. closing the connection)
     // * When receiving something invalid, sending an RST_STREAM using the `reset` method.
     //   This will automatically cause a transition to the CLOSED state.
     else {
       this._log.error(info, 'Received illegal frame.');
--- a/testing/xpcshell/node-http2/package.json
+++ b/testing/xpcshell/node-http2/package.json
@@ -1,26 +1,26 @@
 {
   "name": "http2",
-  "version": "3.2.0",
+  "version": "3.3.6",
   "description": "An HTTP/2 client and server implementation",
   "main": "lib/index.js",
   "engines" : {
-    "node" : ">=0.10.19"
+    "node" : ">=0.12.0"
   },
   "devDependencies": {
     "istanbul": "*",
     "chai": "*",
     "mocha": "*",
     "docco": "*",
     "bunyan": "*"
   },
   "scripts": {
     "test": "istanbul test _mocha -- --reporter spec --slow 500 --timeout 15000",
-    "doc": "docco lib/* --output doc --layout parallel --css doc/docco.css"
+    "doc": "docco lib/* --output doc --layout parallel --template root.jst --css doc/docco.css && docco lib/protocol/* --output doc/protocol --layout parallel --template protocol.jst --css doc/docco.css"
   },
   "repository": {
     "type": "git",
     "url": "https://github.com/molnarg/node-http2.git"
   },
   "homepage": "https://github.com/molnarg/node-http2",
   "bugs": {
     "url": "https://github.com/molnarg/node-http2/issues"
--- a/testing/xpcshell/node-http2/test/flow.js
+++ b/testing/xpcshell/node-http2/test/flow.js
@@ -1,14 +1,14 @@
 var expect = require('chai').expect;
 var util = require('./util');
 
 var Flow = require('../lib/protocol/flow').Flow;
 
-var MAX_PAYLOAD_SIZE = 4096;
+var MAX_PAYLOAD_SIZE = 16384;
 
 function createFlow(log) {
   var flowControlId = util.random(10, 100);
   var flow = new Flow(flowControlId);
   flow._log = util.log.child(log || {});
   return flow;
 }
 
--- a/testing/xpcshell/node-http2/test/http.js
+++ b/testing/xpcshell/node-http2/test/http.js
@@ -1,51 +1,92 @@
 var expect = require('chai').expect;
 var util = require('./util');
 var fs = require('fs');
 var path = require('path');
+var url = require('url');
+var net = require('net');
 
 var http2 = require('../lib/http');
 var https = require('https');
 
-process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
-
-var options = {
+var serverOptions = {
   key: fs.readFileSync(path.join(__dirname, '../example/localhost.key')),
   cert: fs.readFileSync(path.join(__dirname, '../example/localhost.crt')),
+  rejectUnauthorized: true,
   log: util.serverLog
 };
 
-http2.globalAgent = new http2.Agent({ log: util.clientLog });
+var agentOptions = {
+  key: serverOptions.key,
+  ca: serverOptions.cert,
+  rejectUnauthorized: true,
+  log: util.clientLog
+};
+
+var globalAgent = new http2.Agent(agentOptions);
 
 describe('http.js', function() {
+  beforeEach(function() {
+    http2.globalAgent = globalAgent;
+  });
   describe('Server', function() {
     describe('new Server(options)', function() {
       it('should throw if called without \'plain\' or TLS options', function() {
         expect(function() {
           new http2.Server();
         }).to.throw(Error);
         expect(function() {
           http2.createServer(util.noop);
         }).to.throw(Error);
       });
     });
+    describe('method `listen()`', function () {
+      it('should emit `listening` event', function (done) {
+        var server = http2.createServer(serverOptions);
+
+        server.on('listening', function () {
+          server.close();
+
+          done();
+        })
+
+        server.listen(0);
+      });
+      it('should emit `error` on failure', function (done) {
+        var server = http2.createServer(serverOptions);
+
+        // This TCP server is used to explicitly take a port to make
+        // server.listen() fails.
+        var net = require('net').createServer();
+
+        server.on('error', function () {
+          net.close()
+
+          done();
+        });
+
+        net.listen(0, function () {
+          server.listen(this.address().port);
+        });
+      });
+    });
     describe('property `timeout`', function() {
       it('should be a proxy for the backing HTTPS server\'s `timeout` property', function() {
-        var server = new http2.Server(options);
+        var server = new http2.Server(serverOptions);
         var backingServer = server._server;
         var newTimeout = 10;
         server.timeout = newTimeout;
         expect(server.timeout).to.be.equal(newTimeout);
         expect(backingServer.timeout).to.be.equal(newTimeout);
       });
     });
     describe('method `setTimeout(timeout, [callback])`', function() {
       it('should be a proxy for the backing HTTPS server\'s `setTimeout` method', function() {
-        var server = new http2.Server(options);
+        var server = new http2.Server(serverOptions);
         var backingServer = server._server;
         var newTimeout = 10;
         var newCallback = util.noop;
         backingServer.setTimeout = function(timeout, callback) {
           expect(timeout).to.be.equal(newTimeout);
           expect(callback).to.be.equal(newCallback);
         };
         server.setTimeout(newTimeout, newCallback);
@@ -59,16 +100,41 @@ describe('http.js', function() {
         var backingAgent = agent._httpsAgent;
         var newMaxSockets = backingAgent.maxSockets + 1;
         agent.maxSockets = newMaxSockets;
         expect(agent.maxSockets).to.be.equal(newMaxSockets);
         expect(backingAgent.maxSockets).to.be.equal(newMaxSockets);
       });
     });
     describe('method `request(options, [callback])`', function() {
+      it('should use a new agent for request-specific TLS settings', function(done) {
+        var path = '/x';
+        var message = 'Hello world';
+
+        var server = http2.createServer(serverOptions, function(request, response) {
+          expect(request.url).to.equal(path);
+          response.end(message);
+        });
+
+        server.listen(1234, function() {
+          var options = url.parse('https://localhost:1234' + path);
+          options.key = agentOptions.key;
+          options.ca = agentOptions.ca;
+          options.rejectUnauthorized = true;
+
+          http2.globalAgent = new http2.Agent({ log: util.clientLog });
+          http2.get(options, function(response) {
+            response.on('data', function(data) {
+              expect(data.toString()).to.equal(message);
+              server.close();
+              done();
+            });
+          });
+        });
+      });
       it('should throw when trying to use with \'http\' scheme', function() {
         expect(function() {
           var agent = new http2.Agent({ log: util.clientLog });
           agent.request({ protocol: 'http:' });
         }).to.throw(Error);
       });
     });
   });
@@ -121,24 +187,49 @@ describe('http.js', function() {
           called = true;
         }
       }, once: util.noop };
       var response = new http2.OutgoingResponse(stream);
 
       response.writeHead(200);
       response.writeHead(404);
     });
+    it('field finished should be Boolean', function(){
+      var stream = { _log: util.log, headers: function () {}, once: util.noop };
+      var response = new http2.OutgoingResponse(stream);
+      expect(response.finished).to.be.a('Boolean');
+    });
+    it('field finished should initially be false and then go to true when response completes',function(done){
+      var res;
+      var server = http2.createServer(serverOptions, function(request, response) {
+        res = response;
+        expect(res.finished).to.be.false;
+        response.end('HiThere');
+      });
+      server.listen(1236, function() {
+        http2.get('https://localhost:1236/finished-test', function(response) {
+          response.on('data', function(data){
+            var sink = data; //
+          });
+          response.on('end',function(){
+            expect(res.finished).to.be.true;
+            server.close();
+            done();
+          });
+        });
+      });
+    });
   });
   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) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1234, function() {
           http2.get('https://localhost:1234' + path, function(response) {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
@@ -148,22 +239,22 @@ describe('http.js', function() {
           });
         });
       });
     });
     describe('2 simple request in parallel', function() {
       it('should work as expected', function(originalDone) {
         var path = '/x';
         var message = 'Hello world';
-        done = util.callNTimes(2, function() {
+        var done = util.callNTimes(2, function() {
           server.close();
           originalDone();
         });
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1234, function() {
           http2.get('https://localhost:1234' + path, function(response) {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
@@ -179,17 +270,17 @@ describe('http.js', function() {
         });
       });
     });
     describe('100 simple request in a series', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         var n = 100;
         server.listen(1242, function() {
           doRequest();
           function doRequest() {
@@ -209,17 +300,17 @@ describe('http.js', function() {
         });
       });
     });
     describe('request with payload', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           request.once('data', function(data) {
             expect(data.toString()).to.equal(message);
             response.end();
           });
         });
 
         server.listen(1240, function() {
@@ -239,30 +330,33 @@ describe('http.js', function() {
     });
     describe('request with custom status code and headers', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
         var headerName = 'name';
         var headerValue = 'value';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           // Request URL and headers
           expect(request.url).to.equal(path);
           expect(request.headers[headerName]).to.equal(headerValue);
 
           // A header to be overwritten later
           response.setHeader(headerName, 'to be overwritten');
           expect(response.getHeader(headerName)).to.equal('to be overwritten');
 
           // A header to be deleted
           response.setHeader('nonexistent', 'x');
           response.removeHeader('nonexistent');
           expect(response.getHeader('nonexistent')).to.equal(undefined);
 
+          // A set-cookie header which should always be an array
+          response.setHeader('set-cookie', 'foo');
+
           // Don't send date
           response.sendDate = false;
 
           // Specifying more headers, the status code and a reason phrase with `writeHead`
           var moreHeaders = {};
           moreHeaders[headerName] = headerValue;
           response.writeHead(600, 'to be discarded', moreHeaders);
           expect(response.getHeader(headerName)).to.equal(headerValue);
@@ -279,16 +373,18 @@ describe('http.js', function() {
             port: 1239,
             path: path,
             headers: headers
           });
           request.end();
           request.on('response', function(response) {
             expect(response.headers[headerName]).to.equal(headerValue);
             expect(response.headers['nonexistent']).to.equal(undefined);
+            expect(response.headers['set-cookie']).to.an.instanceof(Array)
+            expect(response.headers['set-cookie']).to.deep.equal(['foo'])
             expect(response.headers['date']).to.equal(undefined);
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
               server.close();
               done();
             });
           });
         });
@@ -347,17 +443,17 @@ describe('http.js', function() {
         });
       });
     });
     describe('request to an HTTPS/1 server', function() {
       it('should fall back to HTTPS/1 successfully', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = https.createServer(options, function(request, response) {
+        var server = https.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(5678, function() {
           http2.get('https://localhost:5678' + path, function(response) {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
@@ -366,22 +462,22 @@ describe('http.js', function() {
           });
         });
       });
     });
     describe('2 parallel request to an HTTPS/1 server', function() {
       it('should fall back to HTTPS/1 successfully', function(originalDone) {
         var path = '/x';
         var message = 'Hello world';
-        done = util.callNTimes(2, function() {
+        var done = util.callNTimes(2, function() {
           server.close();
           originalDone();
         });
 
-        var server = https.createServer(options, function(request, response) {
+        var server = https.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(6789, function() {
           http2.get('https://localhost:6789' + path, function(response) {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
@@ -397,37 +493,39 @@ describe('http.js', function() {
         });
       });
     });
     describe('HTTPS/1 request to a HTTP/2 server', function() {
       it('should fall back to HTTPS/1 successfully', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1236, function() {
-          https.get('https://localhost:1236' + path, function(response) {
+          var options = url.parse('https://localhost:1236' + path);
+          options.agent = new https.Agent(agentOptions);
+          https.get(options, function(response) {
             response.on('data', function(data) {
               expect(data.toString()).to.equal(message);
               done();
             });
           });
         });
       });
     });
     describe('two parallel request', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1237, function() {
           done = util.callNTimes(2, done);
           // 1. request
           http2.get('https://localhost:1237' + path, function(response) {
@@ -446,17 +544,17 @@ describe('http.js', function() {
         });
       });
     });
     describe('two subsequent request', function() {
       it('should use the same HTTP/2 connection', function(done) {
         var path = '/x';
         var message = 'Hello world';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           response.end(message);
         });
 
         server.listen(1238, function() {
           // 1. request
           http2.get('https://localhost:1238' + path, function(response) {
             response.on('data', function(data) {
@@ -469,24 +567,122 @@ describe('http.js', function() {
                   done();
                 });
               });
             });
           });
         });
       });
     });
+    describe('https server node module specification conformance', function() {
+      it('should provide API for remote HTTP 1.1 client address', function(done) {
+        var remoteAddress = null;
+        var remotePort = null;
+
+        var server = http2.createServer(serverOptions, function(request, response) {
+          // HTTPS 1.1 client with Node 0.10 server
+          if (!request.remoteAddress) {
+            if (request.socket.socket) {
+              remoteAddress = request.socket.socket.remoteAddress;
+              remotePort = request.socket.socket.remotePort;
+            } else {
+              remoteAddress = request.socket.remoteAddress;
+              remotePort = request.socket.remotePort;
+            }
+          } else {
+            // HTTPS 1.1/2.0 client with Node 0.12 server
+            remoteAddress = request.remoteAddress;
+            remotePort = request.remotePort;
+          }
+          response.write('Pong');
+          response.end();
+        });
+
+        server.listen(1259, 'localhost', function() {
+          var request = https.request({
+            host: 'localhost',
+            port: 1259,
+            path: '/',
+            ca: serverOptions.cert
+          });
+          request.write('Ping');
+          request.end();
+          request.on('response', function(response) {
+            response.on('data', function(data) {
+              var localAddress = response.socket.address();
+              expect(remoteAddress).to.equal(localAddress.address);
+              expect(remotePort).to.equal(localAddress.port);
+              server.close();
+              done();
+            });
+          });
+        });
+      });
+      it('should provide API for remote HTTP 2.0 client address', function(done) {
+        var remoteAddress = null;
+        var remotePort = null;
+        var localAddress = null;
+
+        var server = http2.createServer(serverOptions, function(request, response) {
+          remoteAddress = request.remoteAddress;
+          remotePort = request.remotePort;
+          response.write('Pong');
+          response.end();
+        });
+
+        server.listen(1258, 'localhost', function() {
+          var request = http2.request({
+            host: 'localhost',
+            port: 1258,
+            path: '/'
+          });
+          request.write('Ping');
+          globalAgent.on('false:localhost:1258', function(endpoint) {
+            localAddress = endpoint.socket.address();
+          });
+          request.end();
+          request.on('response', function(response) {
+            response.on('data', function(data) {
+              expect(remoteAddress).to.equal(localAddress.address);
+              expect(remotePort).to.equal(localAddress.port);
+              server.close();
+              done();
+            });
+          });
+        });
+      });
+      it('should expose net.Socket as .socket and .connection', function(done) {
+        var server = http2.createServer(serverOptions, function(request, response) {
+          expect(request.socket).to.equal(request.connection);
+          expect(request.socket).to.be.instanceof(net.Socket);
+          response.write('Pong');
+          response.end();
+          done();
+        });
+
+        server.listen(1248, 'localhost', function() {
+          var request = https.request({
+            host: 'localhost',
+            port: 1248,
+            path: '/',
+            ca: serverOptions.cert
+          });
+          request.write('Ping');
+          request.end();
+        });
+      });
+    });
     describe('request and response with trailers', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
         var requestTrailers = { 'content-md5': 'x' };
         var responseTrailers = { 'content-md5': 'y' };
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           request.on('data', util.noop);
           request.once('end', function() {
             expect(request.trailers).to.deep.equal(requestTrailers);
             response.write(message);
             response.addTrailers(responseTrailers);
             response.end();
           });
@@ -501,24 +697,70 @@ describe('http.js', function() {
             response.once('end', function() {
               expect(response.trailers).to.deep.equal(responseTrailers);
               done();
             });
           });
         });
       });
     });
+    describe('Handle socket error', function () {
+      it('HTTPS on Connection Refused error', function (done) {
+        var path = '/x';
+        var request = http2.request('https://127.0.0.1:6666' + path);
+
+        request.on('error', function (err) {
+          expect(err.errno).to.equal('ECONNREFUSED');
+          done();
+        });
+
+        request.on('response', function (response) {
+          server._server._handle.destroy();
+
+          response.on('data', util.noop);
+
+          response.once('end', function () {
+            done(new Error('Request should have failed'));
+          });
+        });
+
+        request.end();
+
+      });
+      it('HTTP on Connection Refused error', function (done) {
+        var path = '/x';
+
+        var request = http2.raw.request('http://127.0.0.1:6666' + path);
+
+        request.on('error', function (err) {
+          expect(err.errno).to.equal('ECONNREFUSED');
+          done();
+        });
+
+        request.on('response', function (response) {
+          server._server._handle.destroy();
+
+          response.on('data', util.noop);
+
+          response.once('end', function () {
+            done(new Error('Request should have failed'));
+          });
+        });
+
+        request.end();
+      });
+    });
     describe('server push', function() {
       it('should work as expected', function(done) {
         var path = '/x';
         var message = 'Hello world';
         var pushedPath = '/y';
         var pushedMessage = 'Hello world 2';
 
-        var server = http2.createServer(options, function(request, response) {
+        var server = http2.createServer(serverOptions, function(request, response) {
           expect(request.url).to.equal(path);
           var push1 = response.push('/y');
           push1.end(pushedMessage);
           var push2 = response.push({ path: '/y', protocol: 'https:' });
           push2.end(pushedMessage);
           response.end(message);
         });
 
--- a/testing/xpcshell/node-http2/test/stream.js
+++ b/testing/xpcshell/node-http2/test/stream.js
@@ -187,17 +187,17 @@ describe('stream.js', function() {
           });
         });
 
         // CLOSED state as a result of incoming END_STREAM (or RST_STREAM)
         var stream = createStream();
         stream.headers({});
         stream.end();
         stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
-        example_frames.slice(1).forEach(function(invalid_frame) {
+        example_frames.slice(2).forEach(function(invalid_frame) {
           invalid_frame.count_change = util.noop;
           expect(stream._transition.bind(stream, false, invalid_frame)).to.throw('Uncaught, unspecified "error" event.');
         });
 
         // CLOSED state as a result of outgoing END_STREAM
         stream = createStream();
         stream.upstream.write({ type: 'HEADERS', headers:{}, flags: { END_STREAM: true }, count_change: util.noop });
         stream.headers({});