testing/xpcshell/node-spdy/lib/spdy/protocol/v3/framer.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 var framer = exports;
michael@0 2
michael@0 3 var spdy = require('../../../spdy'),
michael@0 4 Buffer = require('buffer').Buffer,
michael@0 5 protocol = require('./');
michael@0 6
michael@0 7 //
michael@0 8 // ### function Framer (deflate, inflate)
michael@0 9 // #### @deflate {zlib.Deflate} Deflate stream
michael@0 10 // #### @inflate {zlib.Inflate} Inflate stream
michael@0 11 // Framer constructor
michael@0 12 //
michael@0 13 function Framer(deflate, inflate) {
michael@0 14 this.version = 3;
michael@0 15 this.deflate = deflate;
michael@0 16 this.inflate = inflate;
michael@0 17 }
michael@0 18 exports.Framer = Framer;
michael@0 19
michael@0 20 //
michael@0 21 // ### function execute (header, body, callback)
michael@0 22 // #### @header {Object} Frame headers
michael@0 23 // #### @body {Buffer} Frame's body
michael@0 24 // #### @callback {Function} Continuation callback
michael@0 25 // Parse frame (decompress data and create streams)
michael@0 26 //
michael@0 27 Framer.prototype.execute = function execute(header, body, callback) {
michael@0 28 // SYN_STREAM or SYN_REPLY
michael@0 29 if (header.type === 0x01 || header.type === 0x02) {
michael@0 30 var frame = protocol.parseSynHead(header.type, header.flags, body);
michael@0 31
michael@0 32 body = body.slice(frame._offset);
michael@0 33
michael@0 34 this.inflate(body, function(err, chunks, length) {
michael@0 35 if (err) return callback(err);
michael@0 36
michael@0 37 var pairs = new Buffer(length);
michael@0 38 for (var i = 0, offset = 0; i < chunks.length; i++) {
michael@0 39 chunks[i].copy(pairs, offset);
michael@0 40 offset += chunks[i].length;
michael@0 41 }
michael@0 42
michael@0 43 frame.headers = protocol.parseHeaders(pairs);
michael@0 44 frame.url = frame.headers.path || '';
michael@0 45
michael@0 46 callback(null, frame);
michael@0 47 });
michael@0 48 // RST_STREAM
michael@0 49 } else if (header.type === 0x03) {
michael@0 50 callback(null, protocol.parseRst(body));
michael@0 51 // SETTINGS
michael@0 52 } else if (header.type === 0x04) {
michael@0 53 callback(null, protocol.parseSettings(body));
michael@0 54 } else if (header.type === 0x05) {
michael@0 55 callback(null, { type: 'NOOP' });
michael@0 56 // PING
michael@0 57 } else if (header.type === 0x06) {
michael@0 58 callback(null, { type: 'PING', pingId: body });
michael@0 59 // GOAWAY
michael@0 60 } else if (header.type === 0x07) {
michael@0 61 callback(null, protocol.parseGoaway(body));
michael@0 62 } else if (header.type === 0x09) {
michael@0 63 callback(null, protocol.parseWindowUpdate(body));
michael@0 64 } else {
michael@0 65 callback(null, { type: 'unknown: ' + header.type, body: body });
michael@0 66 }
michael@0 67 };
michael@0 68
michael@0 69 //
michael@0 70 // internal, converts object into spdy dictionary
michael@0 71 //
michael@0 72 function headersToDict(headers, preprocess) {
michael@0 73 function stringify(value) {
michael@0 74 if (value !== undefined) {
michael@0 75 if (Array.isArray(value)) {
michael@0 76 return value.join('\x00');
michael@0 77 } else if (typeof value === 'string') {
michael@0 78 return value;
michael@0 79 } else {
michael@0 80 return value.toString();
michael@0 81 }
michael@0 82 } else {
michael@0 83 return '';
michael@0 84 }
michael@0 85 }
michael@0 86
michael@0 87 // Lower case of all headers keys
michael@0 88 var loweredHeaders = {};
michael@0 89 Object.keys(headers || {}).map(function(key) {
michael@0 90 loweredHeaders[key.toLowerCase()] = headers[key];
michael@0 91 });
michael@0 92
michael@0 93 // Allow outer code to add custom headers or remove something
michael@0 94 if (preprocess) preprocess(loweredHeaders);
michael@0 95
michael@0 96 // Transform object into kv pairs
michael@0 97 var len = 4,
michael@0 98 pairs = Object.keys(loweredHeaders).filter(function(key) {
michael@0 99 var lkey = key.toLowerCase();
michael@0 100 return lkey !== 'connection' && lkey !== 'keep-alive' &&
michael@0 101 lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
michael@0 102 }).map(function(key) {
michael@0 103 var klen = Buffer.byteLength(key),
michael@0 104 value = stringify(loweredHeaders[key]),
michael@0 105 vlen = Buffer.byteLength(value);
michael@0 106
michael@0 107 len += 8 + klen + vlen;
michael@0 108 return [klen, key, vlen, value];
michael@0 109 }),
michael@0 110 result = new Buffer(len);
michael@0 111
michael@0 112 result.writeUInt32BE(pairs.length, 0, true);
michael@0 113
michael@0 114 var offset = 4;
michael@0 115 pairs.forEach(function(pair) {
michael@0 116 // Write key length
michael@0 117 result.writeUInt32BE(pair[0], offset, true);
michael@0 118 // Write key
michael@0 119 result.write(pair[1], offset + 4);
michael@0 120
michael@0 121 offset += pair[0] + 4;
michael@0 122
michael@0 123 // Write value length
michael@0 124 result.writeUInt32BE(pair[2], offset, true);
michael@0 125 // Write value
michael@0 126 result.write(pair[3], offset + 4);
michael@0 127
michael@0 128 offset += pair[2] + 4;
michael@0 129 });
michael@0 130
michael@0 131 return result;
michael@0 132 };
michael@0 133
michael@0 134 Framer.prototype._synFrame = function _synFrame(type, id, assoc, priority, dict,
michael@0 135 callback) {
michael@0 136 // Compress headers
michael@0 137 this.deflate(dict, function (err, chunks, size) {
michael@0 138 if (err) return callback(err);
michael@0 139
michael@0 140 var offset = type === 'SYN_STREAM' ? 18 : 12,
michael@0 141 total = (type === 'SYN_STREAM' ? 10 : 4) + size,
michael@0 142 frame = new Buffer(offset + size);;
michael@0 143
michael@0 144 frame.writeUInt16BE(0x8003, 0, true); // Control + Version
michael@0 145 frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
michael@0 146 frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
michael@0 147 frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
michael@0 148
michael@0 149 if (type === 'SYN_STREAM') {
michael@0 150 frame[4] = 2;
michael@0 151 frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
michael@0 152 }
michael@0 153
michael@0 154 frame.writeUInt8(priority & 0x7, 16, true); // Priority
michael@0 155
michael@0 156 for (var i = 0; i < chunks.length; i++) {
michael@0 157 chunks[i].copy(frame, offset);
michael@0 158 offset += chunks[i].length;
michael@0 159 }
michael@0 160
michael@0 161 callback(null, frame);
michael@0 162 });
michael@0 163 };
michael@0 164
michael@0 165 //
michael@0 166 // ### function replyFrame (id, code, reason, headers, callback)
michael@0 167 // #### @id {Number} Stream ID
michael@0 168 // #### @code {Number} HTTP Status Code
michael@0 169 // #### @reason {String} (optional)
michael@0 170 // #### @headers {Object|Array} (optional) HTTP headers
michael@0 171 // #### @callback {Function} Continuation function
michael@0 172 // Sends SYN_REPLY frame
michael@0 173 //
michael@0 174 Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
michael@0 175 callback) {
michael@0 176 var dict = headersToDict(headers, function(headers) {
michael@0 177 headers[':status'] = code + ' ' + reason;
michael@0 178 headers[':version'] = 'HTTP/1.1';
michael@0 179 });
michael@0 180
michael@0 181 this._synFrame('SYN_REPLY', id, null, 0, dict, callback);
michael@0 182 };
michael@0 183
michael@0 184 //
michael@0 185 // ### function streamFrame (id, assoc, headers, callback)
michael@0 186 // #### @id {Number} stream id
michael@0 187 // #### @assoc {Number} associated stream id
michael@0 188 // #### @meta {Object} meta headers ( method, scheme, url, version )
michael@0 189 // #### @headers {Object} stream headers
michael@0 190 // #### @callback {Function} continuation callback
michael@0 191 // Create SYN_STREAM frame
michael@0 192 // (needed for server push and testing)
michael@0 193 //
michael@0 194 Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
michael@0 195 callback) {
michael@0 196 var dict = headersToDict(headers, function(headers) {
michael@0 197 headers[':status'] = 200;
michael@0 198 headers[':version'] = meta.version || 'HTTP/1.1';
michael@0 199 headers[':path'] = meta.path;
michael@0 200 headers[':scheme'] = meta.scheme || 'https';
michael@0 201 headers[':host'] = meta.host;
michael@0 202 });
michael@0 203
michael@0 204 this._synFrame('SYN_STREAM', id, assoc, meta.priority, dict, callback);
michael@0 205 };
michael@0 206
michael@0 207 //
michael@0 208 // ### function dataFrame (id, fin, data)
michael@0 209 // #### @id {Number} Stream id
michael@0 210 // #### @fin {Bool} Is this data frame last frame
michael@0 211 // #### @data {Buffer} Response data
michael@0 212 // Sends DATA frame
michael@0 213 //
michael@0 214 Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
michael@0 215 if (!fin && !data.length) return [];
michael@0 216
michael@0 217 var frame = new Buffer(8 + data.length);
michael@0 218
michael@0 219 frame.writeUInt32BE(id & 0x7fffffff, 0, true);
michael@0 220 frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
michael@0 221 frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
michael@0 222
michael@0 223 if (data.length) data.copy(frame, 8);
michael@0 224
michael@0 225 return frame;
michael@0 226 };
michael@0 227
michael@0 228 //
michael@0 229 // ### function pingFrame (id)
michael@0 230 // #### @id {Buffer} Ping ID
michael@0 231 // Sends PING frame
michael@0 232 //
michael@0 233 Framer.prototype.pingFrame = function pingFrame(id) {
michael@0 234 var header = new Buffer(12);
michael@0 235
michael@0 236 header.writeUInt32BE(0x80030006, 0, true); // Version and type
michael@0 237 header.writeUInt32BE(0x00000004, 4, true); // Length
michael@0 238 id.copy(header, 8, 0, 4); // ID
michael@0 239
michael@0 240 return header;
michael@0 241 };
michael@0 242
michael@0 243 //
michael@0 244 // ### function rstFrame (id, code)
michael@0 245 // #### @id {Number} Stream ID
michael@0 246 // #### @code {NUmber} RST Code
michael@0 247 // Sends PING frame
michael@0 248 //
michael@0 249 Framer.prototype.rstFrame = function rstFrame(id, code) {
michael@0 250 var header;
michael@0 251
michael@0 252 if (!(header = Framer.rstCache[code])) {
michael@0 253 header = new Buffer(16);
michael@0 254
michael@0 255 header.writeUInt32BE(0x80030003, 0, true); // Version and type
michael@0 256 header.writeUInt32BE(0x00000008, 4, true); // Length
michael@0 257 header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
michael@0 258 header.writeUInt32BE(code, 12, true); // Status Code
michael@0 259
michael@0 260 Framer.rstCache[code] = header;
michael@0 261 }
michael@0 262
michael@0 263 return header;
michael@0 264 };
michael@0 265 Framer.rstCache = {};
michael@0 266
michael@0 267 //
michael@0 268 // ### function settingsFrame (options)
michael@0 269 // #### @options {Object} settings frame options
michael@0 270 // Sends SETTINGS frame with MAX_CONCURRENT_STREAMS and initial window
michael@0 271 //
michael@0 272 Framer.prototype.settingsFrame = function settingsFrame(options) {
michael@0 273 var settings,
michael@0 274 key = options.maxStreams + ':' + options.windowSize;
michael@0 275
michael@0 276 if (!(settings = Framer.settingsCache[key])) {
michael@0 277 settings = new Buffer(28);
michael@0 278
michael@0 279 settings.writeUInt32BE(0x80030004, 0, true); // Version and type
michael@0 280 settings.writeUInt32BE((4 + 8 * 2) & 0x00FFFFFF, 4, true); // length
michael@0 281 settings.writeUInt32BE(0x00000002, 8, true); // Count of entries
michael@0 282
michael@0 283 settings.writeUInt32BE(0x01000004, 12, true); // Entry ID and Persist flag
michael@0 284 settings.writeUInt32BE(options.maxStreams & 0x7fffffff, 16, true);
michael@0 285
michael@0 286 settings.writeUInt32BE(0x01000007, 20, true); // Entry ID and Persist flag
michael@0 287 settings.writeUInt32BE(options.windowSize & 0x7fffffff, 24, true);
michael@0 288
michael@0 289 Framer.settingsCache[key] = settings;
michael@0 290 }
michael@0 291
michael@0 292 return settings;
michael@0 293 };
michael@0 294 Framer.settingsCache = {};
michael@0 295
michael@0 296 //
michael@0 297 // ### function windowSizeFrame (size)
michael@0 298 // #### @size {Number} data transfer window size
michael@0 299 // Sends SETTINGS frame with window size
michael@0 300 //
michael@0 301 Framer.prototype.windowSizeFrame = function windowSizeFrame(size) {
michael@0 302 var settings;
michael@0 303
michael@0 304 if (!(settings = Framer.windowSizeCache[size])) {
michael@0 305 settings = new Buffer(20);
michael@0 306
michael@0 307 settings.writeUInt32BE(0x80030004, 0, true); // Version and type
michael@0 308 settings.writeUInt32BE((4 + 8) & 0x00FFFFFF, 4, true); // length
michael@0 309 settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
michael@0 310
michael@0 311 settings.writeUInt32BE(0x01000007, 12, true); // Entry ID and Persist flag
michael@0 312 settings.writeUInt32BE(size & 0x7fffffff, 16, true); // Window Size (KB)
michael@0 313
michael@0 314 Framer.windowSizeCache[size] = settings;
michael@0 315 }
michael@0 316
michael@0 317 return settings;
michael@0 318 };
michael@0 319 Framer.windowSizeCache = {};
michael@0 320
michael@0 321 //
michael@0 322 // ### function windowUpdateFrame (id)
michael@0 323 // #### @id {Buffer} WindowUpdate ID
michael@0 324 // Sends WINDOW_UPDATE frame
michael@0 325 //
michael@0 326 Framer.prototype.windowUpdateFrame = function windowUpdateFrame(id, delta) {
michael@0 327 var header = new Buffer(16);
michael@0 328
michael@0 329 header.writeUInt32BE(0x80030009, 0, true); // Version and type
michael@0 330 header.writeUInt32BE(0x00000008, 4, true); // Length
michael@0 331 header.writeUInt32BE(id & 0x7fffffff, 8, true); // ID
michael@0 332 header.writeUInt32BE(delta & 0x7fffffff, 12, true); // delta
michael@0 333
michael@0 334 return header;
michael@0 335 };

mercurial