Bug 1337791 - http/2 origin frame extension test 3/3 r=nwgh draft
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 03 Apr 2017 17:24:49 -0400
changeset 555241 4693886d91800b807f40523f1459d0cbbcf20d45
parent 555240 d3605a438c7397726c100b26db082adb87eeac10
child 622575 14b3d59469550a66001ad3bc3c44bec31ac4434a
push id52202
push userbmo:mcmanus@ducksong.com
push dateMon, 03 Apr 2017 21:42:06 +0000
reviewersnwgh
bugs1337791
milestone55.0a1
Bug 1337791 - http/2 origin frame extension test 3/3 r=nwgh MozReview-Commit-ID: 4uggfoYu9nf
netwerk/test/unit/test_origin.js
netwerk/test/unit/xpcshell.ini
testing/xpcshell/moz-http2/http2-cert.pem
testing/xpcshell/moz-http2/moz-http2.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/stream.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_origin.js
@@ -0,0 +1,254 @@
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+var h2Port;
+var prefs;
+var spdypref;
+var http2pref;
+
+function run_test() {
+  var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+  h2Port = env.get("MOZHTTP2_PORT");
+  do_check_neq(h2Port, null);
+  do_check_neq(h2Port, "");
+
+  // Set to allow the cert presented by our H2 server
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+  http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+  prefs.setCharPref("network.dns.localDomains", "foo.example.com, alt1.example.com");
+
+  // The moz-http2 cert is for {foo, alt1, alt2}.example.com and is signed by CA.cert.der
+  // so add that cert to the trust list as a signing cert.
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+  addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
+  doTest1();
+}
+
+function resetPrefs() {
+  prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+  prefs.clearUserPref("network.dns.localDomains");
+}
+
+function readFile(file) {
+  let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+                  .createInstance(Ci.nsIFileInputStream);
+  fstream.init(file, -1, 0, 0);
+  let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+  fstream.close();
+  return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+  let certFile = do_get_file(filename, false);
+  let der = readFile(certFile);
+  certdb.addCert(der, trustString);
+}
+
+function makeChan(origin) {
+  return NetUtil.newChannel({
+    uri: origin,
+    loadUsingSystemPrincipal: true
+  }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+var nextTest;
+var nextPortExpectedToBeSame = false;
+var currentPort = 0;
+var forceReload = false;
+var forceFailListener = false;
+
+var Listener = function() {};
+Listener.prototype.clientPort = 0;
+Listener.prototype = {
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+
+    if (!Components.isSuccessCode(request.status)) {
+      do_throw("Channel should have a success code! (" + request.status + ")");
+    }
+    do_check_eq(request.responseStatus, 200);
+    this.clientPort = parseInt(request.getResponseHeader("x-client-port"));
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(Components.isSuccessCode(status));
+    if (nextPortExpectedToBeSame) {
+     do_check_eq(currentPort, this.clientPort);
+    } else {
+     do_check_neq(currentPort, this.clientPort);
+    }
+    currentPort = this.clientPort;
+    nextTest();
+    do_test_finished();
+  }
+};
+
+var FailListener = function() {};
+FailListener.prototype = {
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    do_check_true(request instanceof Components.interfaces.nsIHttpChannel);
+    do_check_false(Components.isSuccessCode(request.status));
+  },
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    read_stream(stream, cnt);
+  },
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_false(Components.isSuccessCode(request.status));
+    nextTest();
+    do_test_finished();
+  }
+};
+
+function testsDone()
+{
+  dump("testsDone\n");
+  resetPrefs();
+}
+
+function doTest()
+{
+  dump("execute doTest " + origin + "\n");
+  var chan = makeChan(origin);
+  var listener;
+  if (!forceFailListener) {
+    listener = new Listener();
+  } else {
+    listener = new FailListener();
+  }
+  forceFailListener = false;
+
+  if (!forceReload) {
+    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+  } else {
+    chan.loadFlags = Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+                     Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+  }
+  forceReload = false;
+  chan.asyncOpen2(listener);
+}
+
+function doTest1()
+{
+  dump("doTest1()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-1";
+  nextTest = doTest2;
+  nextPortExpectedToBeSame = false;
+  do_test_pending();
+  doTest();
+}
+
+function doTest2()
+{
+  // plain connection reuse
+  dump("doTest2()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-2";
+  nextTest = doTest3;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest3()
+{
+  // 7540 style coalescing
+  dump("doTest3()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-3";
+  nextTest = doTest4;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest4()
+{
+  // forces an empty origin frame to be omitted
+  dump("doTest4()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-4";
+  nextTest = doTest5;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest5()
+{
+  // 7540 style coalescing should not work due to empty origin set
+  dump("doTest5()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-5";
+  nextTest = doTest6;
+  nextPortExpectedToBeSame = false;
+  do_test_pending();
+  doTest();
+}
+
+function doTest6()
+{
+  // get a fresh connection with alt1 and alt2 in origin set
+  // note that there is no dns for alt2
+  dump("doTest6()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-6";
+  nextTest = doTest7;
+  nextPortExpectedToBeSame = false;
+  forceReload = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest7()
+{
+  // check conn reuse to ensure sni is implicit in origin set
+  dump("doTest7()\n");
+  origin = "https://foo.example.com:" + h2Port + "/origin-7";
+  nextTest = doTest8;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest8()
+{
+  // alt1 is in origin set (and is 7540 eligible)
+  dump("doTest8()\n");
+  origin = "https://alt1.example.com:" + h2Port + "/origin-8";
+  nextTest = doTest9;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest9()
+{
+  // alt2 is in origin set but does not have dns
+  dump("doTest9()\n");
+  origin = "https://alt2.example.com:" + h2Port + "/origin-9";
+  nextTest = doTest10;
+  nextPortExpectedToBeSame = true;
+  do_test_pending();
+  doTest();
+}
+
+function doTest10()
+{
+  // bar is in origin set but does not have dns like alt2
+  // but the cert is not valid for bar. so expect a failure
+  dump("doTest10()\n");
+  origin = "https://bar.example.com:" + h2Port + "/origin-10";
+  nextTest = testsDone;
+  nextPortExpectedToBeSame = false;
+  forceFailListener = true;
+  do_test_pending();
+  doTest();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -245,16 +245,17 @@ run-sequentially = node server exception
 [test_multipart_streamconv_missing_boundary_lead_dashes.js]
 [test_multipart_streamconv-byte-by-byte.js]
 [test_nestedabout_serialize.js]
 [test_net_addr.js]
 # Bug 732363: test fails on windows for unknown reasons.
 skip-if = os == "win"
 [test_nojsredir.js]
 [test_offline_status.js]
+[test_origin.js]
 [test_original_sent_received_head.js]
 [test_parse_content_type.js]
 [test_permmgr.js]
 [test_plaintext_sniff.js]
 [test_post.js]
 [test_private_necko_channel.js]
 [test_private_cookie_changed.js]
 [test_progress.js]
--- a/testing/xpcshell/moz-http2/http2-cert.pem
+++ b/testing/xpcshell/moz-http2/http2-cert.pem
@@ -1,79 +1,24 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=US, ST=Maine, O=CA Example
-        Validity
-            Not Before: Apr 29 05:29:19 2015 GMT
-            Not After : Apr 26 05:29:19 2025 GMT
-        Subject: C=US, ST=Maine, O=Example Com, CN=foo.example.com
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (2048 bit)
-                Modulus:
-                    00:cf:ff:c0:27:3b:a3:11:b5:7f:5d:4f:22:f9:75:
-                    48:47:d9:3a:ce:9b:66:82:4e:e4:ae:ab:78:d3:4c:
-                    3a:9a:5c:37:97:b2:7b:4e:2a:54:77:16:2a:3e:6f:
-                    52:ee:4b:49:46:1d:6b:18:9a:ed:b1:ad:64:9f:8b:
-                    e5:fa:e4:60:7b:39:0e:db:e8:b4:2d:4b:e8:ab:37:
-                    e8:90:ec:eb:0f:3e:6b:40:7a:d1:da:e6:68:b3:f4:
-                    f6:68:54:5b:27:90:6d:c2:c3:04:de:85:23:2b:3c:
-                    66:4e:06:79:58:93:a1:71:d7:ec:74:55:a4:84:9d:
-                    41:22:2a:7a:76:ae:56:b1:6f:15:2d:f2:f5:9c:64:
-                    3e:4f:0f:6e:8f:b6:28:66:e9:89:04:5d:1d:21:77:
-                    f8:03:d3:89:ed:7c:f4:3b:42:02:c8:8d:de:47:74:
-                    1f:4a:5d:fe:8d:d1:57:37:08:54:bf:89:d8:f7:27:
-                    22:a7:2a:5d:aa:d5:b0:61:22:9b:96:75:ee:ab:09:
-                    ca:a9:cb:2b:1e:88:7c:5a:53:7e:5f:88:c4:43:ea:
-                    e8:a7:db:35:6c:b2:89:ad:98:e0:96:c9:83:c4:c1:
-                    e7:2a:5c:f8:99:5c:9e:01:9c:e6:99:bd:18:5c:69:
-                    d4:10:f1:46:88:37:0b:4e:76:5f:6a:1a:21:c2:a4:
-                    16:d1
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Subject Key Identifier: 
-                76:BC:13:90:F7:85:1B:1C:24:A1:CC:65:8A:4F:4C:0C:7F:10:D3:F5
-            X509v3 Authority Key Identifier: 
-                keyid:F7:FC:76:AF:C5:1A:E9:C9:42:6C:38:DF:8B:07:9E:2B:2C:E5:8E:20
-
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            X509v3 Key Usage: 
-                Digital Signature, Key Encipherment
-    Signature Algorithm: sha256WithRSAEncryption
-         03:ab:2a:9e:e5:cd:5c:88:5a:6c:f7:4b:7a:7c:ef:85:2c:31:
-         df:03:79:31:a6:c5:c8:2b:c6:21:a5:33:2b:a0:4b:e2:7e:0a:
-         86:9b:72:25:b6:75:43:41:7c:30:9f:15:b4:9f:34:50:57:eb:
-         87:f9:1e:9f:b6:cd:81:36:92:61:66:d5:fe:e2:c5:ed:de:f1:
-         ce:85:0b:f9:6a:2b:32:4d:29:f1:a9:94:57:a3:0f:74:93:12:
-         c9:0a:28:5e:72:9f:4f:0f:78:f5:84:11:5a:9f:d7:1c:4c:fd:
-         13:d8:3d:4c:f8:dd:4c:c6:1c:fd:63:ee:f5:d5:96:f5:00:2c:
-         e6:bb:c9:4c:d8:6a:19:59:58:2b:d4:05:ab:57:47:1c:49:d6:
-         c5:56:1a:e3:64:10:19:9b:44:3e:74:8b:19:73:28:86:96:b4:
-         d1:2a:49:23:07:25:97:64:8f:1b:1c:64:76:12:e0:df:e3:cf:
-         55:d5:7c:e9:77:d4:69:2f:c7:9a:fd:ce:1a:29:ab:d7:88:68:
-         93:de:75:e4:d6:85:29:e2:b6:b7:59:20:e3:b5:20:b7:e8:0b:
-         23:9b:4c:b4:e8:d9:90:cf:e9:2f:9e:a8:22:a2:ef:6a:68:65:
-         f6:c4:81:ed:75:77:88:01:f2:47:03:1a:de:1f:44:38:47:fa:
-         aa:69:f2:98
 -----BEGIN CERTIFICATE-----
-MIIDVDCCAjygAwIBAgIBATANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJVUzEO
-MAwGA1UECAwFTWFpbmUxEzARBgNVBAoMCkNBIEV4YW1wbGUwHhcNMTUwNDI5MDUy
-OTE5WhcNMjUwNDI2MDUyOTE5WjBNMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFTWFp
-bmUxFDASBgNVBAoMC0V4YW1wbGUgQ29tMRgwFgYDVQQDDA9mb28uZXhhbXBsZS5j
-b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/8AnO6MRtX9dTyL5
-dUhH2TrOm2aCTuSuq3jTTDqaXDeXsntOKlR3Fio+b1LuS0lGHWsYmu2xrWSfi+X6
-5GB7OQ7b6LQtS+irN+iQ7OsPPmtAetHa5miz9PZoVFsnkG3CwwTehSMrPGZOBnlY
-k6Fx1+x0VaSEnUEiKnp2rlaxbxUt8vWcZD5PD26Ptihm6YkEXR0hd/gD04ntfPQ7
-QgLIjd5HdB9KXf6N0Vc3CFS/idj3JyKnKl2q1bBhIpuWde6rCcqpyyseiHxaU35f
-iMRD6uin2zVssomtmOCWyYPEwecqXPiZXJ4BnOaZvRhcadQQ8UaINwtOdl9qGiHC
-pBbRAgMBAAGjWjBYMB0GA1UdDgQWBBR2vBOQ94UbHCShzGWKT0wMfxDT9TAfBgNV
-HSMEGDAWgBT3/HavxRrpyUJsON+LB54rLOWOIDAJBgNVHRMEAjAAMAsGA1UdDwQE
-AwIFoDANBgkqhkiG9w0BAQsFAAOCAQEAA6sqnuXNXIhabPdLenzvhSwx3wN5MabF
-yCvGIaUzK6BL4n4KhptyJbZ1Q0F8MJ8VtJ80UFfrh/ken7bNgTaSYWbV/uLF7d7x
-zoUL+WorMk0p8amUV6MPdJMSyQooXnKfTw949YQRWp/XHEz9E9g9TPjdTMYc/WPu
-9dWW9QAs5rvJTNhqGVlYK9QFq1dHHEnWxVYa42QQGZtEPnSLGXMohpa00SpJIwcl
-l2SPGxxkdhLg3+PPVdV86XfUaS/Hmv3OGimr14hok9515NaFKeK2t1kg47Ugt+gL
-I5tMtOjZkM/pL56oIqLvamhl9sSB7XV3iAHyRwMa3h9EOEf6qmnymA==
+MIID+TCCAuGgAwIBAgIJAKu6XZkGFQ8NMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVNYWluZTETMBEGA1UECgwKQ0EgRXhhbXBsZTAeFw0x
+NzAzMjgwMjIxMjZaFw0yNzAzMjYwMjIxMjZaMIHIMQswCQYDVQQGEwJVUzERMA8G
+A1UECAwITmV3IFlvcmsxEjAQBgNVBAcMCVJvY2hlc3RlcjESMBAGA1UECgwJRW5k
+IFBvaW50MRcwFQYDVQQLDA5UZXN0aW5nIERvbWFpbjFLMEkGCSqGSIb3DQEJARY8
+eW91ci1hZG1pbmlzdHJhdGl2ZS1hZGRyZXNzQHlvdXItYXdlc29tZS1leGlzdGlu
+Zy1kb21haW4uY29tMRgwFgYDVQQDDA9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/8AnO6MRtX9dTyL5dUhH2TrOm2aCTuSu
+q3jTTDqaXDeXsntOKlR3Fio+b1LuS0lGHWsYmu2xrWSfi+X65GB7OQ7b6LQtS+ir
+N+iQ7OsPPmtAetHa5miz9PZoVFsnkG3CwwTehSMrPGZOBnlYk6Fx1+x0VaSEnUEi
+Knp2rlaxbxUt8vWcZD5PD26Ptihm6YkEXR0hd/gD04ntfPQ7QgLIjd5HdB9KXf6N
+0Vc3CFS/idj3JyKnKl2q1bBhIpuWde6rCcqpyyseiHxaU35fiMRD6uin2zVssomt
+mOCWyYPEwecqXPiZXJ4BnOaZvRhcadQQ8UaINwtOdl9qGiHCpBbRAgMBAAGjezB5
+MB8GA1UdIwQYMBaAFPf8dq/FGunJQmw434sHniss5Y4gMAkGA1UdEwQCMAAwCwYD
+VR0PBAQDAgTwMD4GA1UdEQQ3MDWCD2Zvby5leGFtcGxlLmNvbYIQYWx0MS5leGFt
+cGxlLmNvbYIQYWx0Mi5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEARitg
+nxH87RYCMo/wbfSyttkiDmlw1vuEFI6dq9D6iTYcJK9pD9VvQ8e/k0J0FdtnIGBD
+O9+kKuPCRIjJt0mRToQHXI4SFIEqUraI5xA5VdXT2FR5KsshNSw6LjV25gvv0hcI
+6YBOlJ1IzntSA3h7lGGhgqH2ln32hzTQ8ob8F8i3GecOIk6mDkgCHTPRe7tfyTKw
+7c6Z8By6Es84RCQdxXf6AouhJw9SfZl1T5bcy5vDbBcNYenfvueCLezNX6kK7orh
+KsqnxWr2cG8c3X1OIuuvAEUbQ78InOb4OPiQQXcfv+dzxnv7tK6pNRcmMUhabwM8
+J3i97uzqNXPwTFMu3Q==
 -----END CERTIFICATE-----
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -738,16 +738,31 @@ function handleRequest(req, res) {
   else if (u.pathname === "/immutable-test-with-attribute") {
     res.setHeader('Cache-Control', 'max-age=100000, immutable');
     res.setHeader('Etag', '2');
      if (req.headers["if-none-match"]) {
        res.setHeader("x-conditional", "true");
      }
    // default response from here
   }
+  else if (u.pathname === "/origin-4") {
+   var originList = [ ];
+   req.stream.connection.originFrame(originList);
+   res.setHeader("x-client-port", req.remotePort);
+  }
+  else if (u.pathname === "/origin-6") {
+   var originList = [ "https://alt1.example.com:" + serverPort,
+		      "https://alt2.example.com:" + serverPort,
+                      "https://bar.example.com:" + serverPort ];
+   req.stream.connection.originFrame(originList);
+   res.setHeader("x-client-port", req.remotePort);
+  }
+  else if (u.pathname.substring(0,8) === "/origin-") { // test_origin.js coalescing
+    res.setHeader("x-client-port", req.remotePort);
+  }
 
   res.setHeader('Content-Type', 'text/html');
   if (req.httpVersionMajor != 2) {
     res.setHeader('Connection', 'close');
   }
   res.writeHead(200);
   res.end(content);
 }
--- a/testing/xpcshell/node-http2/lib/protocol/connection.js
+++ b/testing/xpcshell/node-http2/lib/protocol/connection.js
@@ -118,17 +118,17 @@ Connection.prototype._initializeStreamMa
   this.on('RECEIVING_SETTINGS_MAX_CONCURRENT_STREAMS', this._updateStreamLimit);
 };
 
 // `_writeControlFrame` is called when there's an incoming frame in the `_control` stream. It
 // broadcasts the message by creating an event on it.
 Connection.prototype._writeControlFrame = function _writeControlFrame(frame) {
   if ((frame.type === 'SETTINGS') || (frame.type === 'PING') ||
       (frame.type === 'GOAWAY') || (frame.type === 'WINDOW_UPDATE') ||
-      (frame.type === 'ALTSVC')) {
+      (frame.type === 'ALTSVC') || (frame.type == 'ORIGIN')) {
     this._log.debug({ frame: frame }, 'Receiving connection level frame');
     this.emit(frame.type, frame);
   } else {
     this._log.error({ frame: frame }, 'Invalid connection level frame');
     this.emit('error', 'PROTOCOL_ERROR');
   }
 };
 
@@ -552,16 +552,27 @@ Connection.prototype._receivePing = func
         ACK: true
       },
       stream: 0,
       data: frame.data
     });
   }
 };
 
+Connection.prototype.originFrame = function originFrame(originList) {
+  this._log.debug(originList, 'emitting origin frame');
+
+  this.push({
+    type: 'ORIGIN',
+    flags: {},
+    stream: 0,
+    originList : originList,
+  });
+};
+
 // Terminating the connection
 Connection.prototype.close = function close(error) {
   if (this._closed) {
     this._log.warn('Trying to close an already closed connection');
     return;
   }
 
   this._log.debug({ error: error }, 'Closing the connection');
--- a/testing/xpcshell/node-http2/lib/protocol/flow.js
+++ b/testing/xpcshell/node-http2/lib/protocol/flow.js
@@ -59,17 +59,16 @@ var INITIAL_WINDOW_SIZE = 65535;
 function Flow(flowControlId) {
   Duplex.call(this, { objectMode: true });
 
   this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
   this._flowControlId = flowControlId;
   this._queue = [];
   this._ended = false;
   this._received = 0;
-  this._blocked = false;
 }
 Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });
 
 // Incoming frames
 // ---------------
 
 // `_receive` is called when there's an incoming frame.
 Flow.prototype._receive = function _receive(frame, callback) {
@@ -152,40 +151,33 @@ 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)
   else if (this._window > 0) {
-    this._blocked = false;
     this._readableState.sync = true; // to avoid reentrant calls
     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) ||                              // * 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',
-      flags: {},
-      stream: this._flowControlId
-    });
+  else {
     this.once('window_update', this._read);
-    this._blocked = true;
   }
 };
 
 var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383
 
 // `read(limit)` is like the `read` of the Readable class, but it guarantess that the 'flow control
 // size' (0 for non-DATA frames, length of the payload for DATA frames) of the returned frame will
 // be under `limit`.
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -1064,36 +1064,34 @@ Deserializer.ALTSVC = function readAltSv
       frame.port = parseInt(hostport[1], 10);
     } else if (chosenAltSvc[i].name == 'ma') {
       frame.maxAge = parseInt(chosenAltSvc[i].value, 10);
     }
     // Otherwise, we just ignore this
   }
 };
 
-// BLOCKED
-// ------------------------------------------------------------
-//
-// The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
-// due to a closed flow control window.
-//
-// The BLOCKED frame does not define any flags and contains no payload.
+frameTypes[0xB] = 'ORIGIN';
+frameFlags.ORIGIN = [];
+typeSpecificAttributes.ORIGIN = ['originList'];
 
-frameTypes[0xB] = 'BLOCKED';
-
-frameFlags.BLOCKED = [];
-
-typeSpecificAttributes.BLOCKED = [];
-
-Serializer.BLOCKED = function writeBlocked(frame, buffers) {
+Serializer.ORIGIN = function writeOrigin(frame, buffers) {
+  for (var i = 0; i < frame.originList.length; i++) {
+    var buffer = new Buffer(2);
+    buffer.writeUInt16BE(frame.originList[i].length, 0);
+    buffers.push(buffer);
+    buffers.push(new Buffer(frame.originList[i], 'ascii'));
+  }
 };
 
-Deserializer.BLOCKED = function readBlocked(buffer, frame) {
+Deserializer.ORIGIN = function readOrigin(buffer, frame) {
+    // ignored
 };
 
+
 // [Error Codes](https://tools.ietf.org/html/rfc7540#section-7)
 // ------------------------------------------------------------
 
 var errorCodes = [
   'NO_ERROR',
   'PROTOCOL_ERROR',
   'INTERNAL_ERROR',
   'FLOW_CONTROL_ERROR',
--- a/testing/xpcshell/node-http2/lib/protocol/stream.js
+++ b/testing/xpcshell/node-http2/lib/protocol/stream.js
@@ -248,17 +248,17 @@ Stream.prototype._writeUpstream = functi
     this._processedHeaders = true;
     this._onHeaders(frame);
   } else if (frame.type === 'PUSH_PROMISE') {
     this._onPromise(frame);
   } else if (frame.type === 'PRIORITY') {
     this._onPriority(frame);
   } else if (frame.type === 'ALTSVC') {
     // TODO
-  } else if (frame.type === 'BLOCKED') {
+  } else if (frame.type === 'ORIGIN') {
     // TODO
   }
 
   // * If it's an invalid stream level frame, emit error
   else if ((frame.type !== 'DATA') &&
            (frame.type !== 'WINDOW_UPDATE') &&
            (frame.type !== 'RST_STREAM')) {
     this._log.error({ frame: frame }, 'Invalid stream level frame');
@@ -408,27 +408,27 @@ function activeState(state) {
 // `_transition` is called every time there's an incoming or outgoing frame. It manages state
 // transitions, and detects stream errors. A stream error is always caused by a frame that is not
 // allowed in the current state.
 Stream.prototype._transition = function transition(sending, frame) {
   var receiving = !sending;
   var connectionError;
   var streamError;
 
-  var DATA = false, HEADERS = false, PRIORITY = false, ALTSVC = false, BLOCKED = false;
+  var DATA = false, HEADERS = false, PRIORITY = false, ALTSVC = false, ORIGIN = false;
   var RST_STREAM = false, PUSH_PROMISE = false, WINDOW_UPDATE = false;
   switch(frame.type) {
     case 'DATA'         : DATA          = true; break;
     case 'HEADERS'      : HEADERS       = true; break;
     case 'PRIORITY'     : PRIORITY      = true; break;
     case 'RST_STREAM'   : RST_STREAM    = true; break;
     case 'PUSH_PROMISE' : PUSH_PROMISE  = true; break;
     case 'WINDOW_UPDATE': WINDOW_UPDATE = true; break;
     case 'ALTSVC'       : ALTSVC        = true; break;
-    case 'BLOCKED'      : BLOCKED       = true; break;
+    case 'ORIGIN'       : ORIGIN        = true; break;
   }
 
   var previousState = this.state;
 
   switch (this.state) {
     // All streams start in the **idle** state. In this state, no frames have been exchanged.
     //
     // * Sending or receiving a HEADERS frame causes the stream to become "open".
@@ -478,17 +478,17 @@ Stream.prototype._transition = function 
     // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
     // * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream.
     // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
     case 'RESERVED_REMOTE':
       if (RST_STREAM) {
         this._setState('CLOSED');
       } else if (receiving && HEADERS) {
         this._setState('HALF_CLOSED_LOCAL');
-      } else if (BLOCKED || PRIORITY) {
+      } else if (PRIORITY || ORIGIN) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **open** state is where both peers can send frames. In this state, sending peers observe
     // advertised stream level flow control limits.
@@ -513,17 +513,17 @@ Stream.prototype._transition = function 
     //
     // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
     //   flag is received, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
     case 'HALF_CLOSED_LOCAL':
       if (RST_STREAM || (receiving && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) {
+      } else if (ORIGIN || ALTSVC || receiving || PRIORITY || (sending && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
     // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
@@ -533,17 +533,17 @@ Stream.prototype._transition = function 
     //   respond with a stream error of type STREAM_CLOSED.
     // * A stream can transition from this state to "closed" by sending a frame that contains a
     //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
     // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
     // * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream.
     case 'HALF_CLOSED_REMOTE':
       if (RST_STREAM || (sending && frame.flags.END_STREAM)) {
         this._setState('CLOSED');
-      } else if (BLOCKED || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) {
+      } else if (ORIGIN || ALTSVC || sending || PRIORITY || (receiving && WINDOW_UPDATE)) {
         /* No state change */
       } else {
         connectionError = 'PROTOCOL_ERROR';
       }
       break;
 
     // The **closed** state is the terminal state.
     //
@@ -564,17 +564,17 @@ Stream.prototype._transition = function 
     //   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 || RST_STREAM || ALTSVC))) {
+           (this._closedWithRst || RST_STREAM || ALTSVC || ORIGIN))) {
         /* 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.