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

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

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

mercurial