Today I begin my investigation of some things I did not quite understand while looking at SPDY requests and SPDY responses in detail. To get up to speed with the node-spdy codebase, I start with an easy one: the SETTINGS frame.
While examining the SPDY response SETTINGS frame last night, I noticed that it does not match up with the spec:
+----------------------------------+Specifically, the ID is wrong. The first octet ought to be an optional flag (0x1 = client should persist this setting; 0x2 = this is a persisted value). The actual value being set, 0x04 is not a valid SPDY SETTINGS frame flag, so I would guess that this is the ID. Per the SPDY draft (#2) spec, an ID of 0x04 is SETTINGS_MAX_CONCURRENT_STREAMS.
80 02 00 04 |1| version | 4 |
+----------------------------------+
00 00 00 0c | Flags (8) | Length (24 bits) |
+----------------------------------+
00 00 00 01 | Number of entries |
+----------------------------------+
04 00 00 00 | ID / |
00 00 00 64 | Value Pairs ... |
Looking through the node-spdy source, I see that a server is created with the following default options:
var Server = core.Server = function(options, requestListener) {Those settings are sent back to the client at the end of the TLS server callback (i.e. for each TLS server connection):
var that = this,
connectionSettings = options.connectionSettings || {
SETTINGS_MAX_CONCURRENT_STREAMS: 100
};
...
Checking out the
...
// Send settings
c.write(createSettingsFrame(c.zlib, connectionSettings));
...
createSettingsFrame
method, I find:/**The zlib context being passed in is required to decompress/compress multiple frames over the course of a single connection. The settings are an object literal like that from the server core (
* Create SETTINGS frame
*/
exports.createSettingsFrame = function(zlib, settings) {
var keys = Object.keys(settings),
keysLen = keys.length,
buff = new Buffer(4 + 8 * keysLen);
// Insert keys count
buff.writeUInt32(keysLen, 0, 'big');
keys.reduce(function(offset, key) {
var raw_key = enums[key];
buff.writeUInt32(raw_key & 0xffffff, offset, 'little');
var value = settings[key];
buff.writeUInt32(value, offset + 4, 'big');
return offset + 8;
}, 4);
return createControlFrame(zlib, {
type: enums.SETTINGS
}, buff);
};
{SETTINGS_MAX_CONCURRENT_STREAMS: 100}
). The createSettingsFrame
function then builds a buffer large enough to hold 4 bytes/octets describing the number of keys being set (one in this case) plus 8 bytes/octets for each key. The number of keys are written to the first four octets (via writeUInt32
/ 32 bits). Next, the code iterates (using a reduce
) over each key writing the ID in the first 32 bits and the value in the second 32 bits.But why the use of little-endian byte order? All packets that I have seen so far have been in big-endian order—something the spec confirms:
All integer values, including length, version, and type, are in network byte order.So I change the ID/Value pair code to read:
...Checking this out in the console, I find:
var raw_key = enums[key];
buff.writeUInt32(raw_key, offset, 'big');
var value = settings[key];
buff.writeUInt32(value, offset + 4, 'big');
return offset + 8;
...
➜ node-spdy git:(master) ✗ nodeMatching that up with the spec, I find:
> var createSettingsFrame = require('./lib/spdy').createSettingsFrame,
... createZLib = require('./lib/spdy').createZLib,
... enums = require('./lib/spdy').enums;
>
> createSettingsFrame(createZLib(), {SETTINGS_MAX_CONCURRENT_STREAMS: 100});
SETTINGS_MAX_CONCURRENT_STREAMS
<Buffer 80 02 00 04 00 00 00 0c 00 00 00 01 00 00 00 04 00 00 00 64>
+----------------------------------+Easy-peasy.
80 02 00 04 |1| 1 | 4 |
+----------------------------------+
00 00 00 0c | Flags (8) | Length (24 bits) |
+----------------------------------+
00 00 00 01 | Number of entries |
+----------------------------------+
00 00 00 04 | ID/ |
00 00 00 64 | Value Pairs... |
A quick sanity check in Wireshark reveals that the packet is coming through as I expect:
0000 80 02 00 04 00 00 00 0c 00 00 00 01 00 00 00 04 ........ ........Still...
0010 00 00 00 64 ...d
I find it very odd that node-spdy is choosing to be so explicit about the endian-ness here. One more sanity check reveals why. Examining the SPDY packets in Chrome's
about:net-internals
panel, I find:t=1306207841256 [st= 8] SPDY_SESSION_RECV_SETTINGSBizarre. I would have expected 4:100, where 4 indicates that I am trying to set
--> settings = ["[0:100]"]
SETTINGS_MAX_CONCURRENT_STREAMS
.If I revert to the little-endian order, then reload, net-internals tells me:
(P) t=1306207452900 [st= 6] SPDY_SESSION_RECV_SETTINGSSo I guess there was a reason for that after all. It just seems to be a reason that violates the spec.
--> settings = ["[4:100]"]
A mystery for another day.
Day #29
No comments:
Post a Comment