Bug 1337791 - http/2 origin frame extension test 3/3 r=nwgh
MozReview-Commit-ID: 4uggfoYu9nf
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.