1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,286 @@ 1.4 +var framer = exports; 1.5 + 1.6 +var spdy = require('../../../spdy'), 1.7 + Buffer = require('buffer').Buffer, 1.8 + protocol = require('./'); 1.9 + 1.10 +// 1.11 +// ### function Framer (deflate, inflate) 1.12 +// #### @deflate {zlib.Deflate} Deflate stream 1.13 +// #### @inflate {zlib.Inflate} Inflate stream 1.14 +// Framer constructor 1.15 +// 1.16 +function Framer(deflate, inflate) { 1.17 + this.version = 2; 1.18 + this.deflate = deflate; 1.19 + this.inflate = inflate; 1.20 +} 1.21 +exports.Framer = Framer; 1.22 + 1.23 + 1.24 +// 1.25 +// ### function execute (header, body, callback) 1.26 +// #### @header {Object} Frame headers 1.27 +// #### @body {Buffer} Frame's body 1.28 +// #### @callback {Function} Continuation callback 1.29 +// Parse frame (decompress data and create streams) 1.30 +// 1.31 +Framer.prototype.execute = function execute(header, body, callback) { 1.32 + // SYN_STREAM or SYN_REPLY 1.33 + if (header.type === 0x01 || header.type === 0x02) { 1.34 + var frame = protocol.parseSynHead(header.type, header.flags, body); 1.35 + 1.36 + body = body.slice(frame._offset); 1.37 + 1.38 + this.inflate(body, function(err, chunks, length) { 1.39 + if (err) return callback(err); 1.40 + 1.41 + var pairs = new Buffer(length); 1.42 + for (var i = 0, offset = 0; i < chunks.length; i++) { 1.43 + chunks[i].copy(pairs, offset); 1.44 + offset += chunks[i].length; 1.45 + } 1.46 + 1.47 + frame.headers = protocol.parseHeaders(pairs); 1.48 + frame.url = frame.headers.url || ''; 1.49 + 1.50 + callback(null, frame); 1.51 + }); 1.52 + // RST_STREAM 1.53 + } else if (header.type === 0x03) { 1.54 + callback(null, protocol.parseRst(body)); 1.55 + // SETTINGS 1.56 + } else if (header.type === 0x04) { 1.57 + callback(null, { type: 'SETTINGS' }); 1.58 + } else if (header.type === 0x05) { 1.59 + callback(null, { type: 'NOOP' }); 1.60 + // PING 1.61 + } else if (header.type === 0x06) { 1.62 + callback(null, { type: 'PING', pingId: body }); 1.63 + // GOAWAY 1.64 + } else if (header.type === 0x07) { 1.65 + callback(null, protocol.parseGoaway(body)); 1.66 + } else { 1.67 + callback(null, { type: 'unknown: ' + header.type, body: body }); 1.68 + } 1.69 +}; 1.70 + 1.71 +// 1.72 +// internal, converts object into spdy dictionary 1.73 +// 1.74 +function headersToDict(headers, preprocess) { 1.75 + function stringify(value) { 1.76 + if (value !== undefined) { 1.77 + if (Array.isArray(value)) { 1.78 + return value.join('\x00'); 1.79 + } else if (typeof value === 'string') { 1.80 + return value; 1.81 + } else { 1.82 + return value.toString(); 1.83 + } 1.84 + } else { 1.85 + return ''; 1.86 + } 1.87 + } 1.88 + 1.89 + // Lower case of all headers keys 1.90 + var loweredHeaders = {}; 1.91 + Object.keys(headers || {}).map(function(key) { 1.92 + loweredHeaders[key.toLowerCase()] = headers[key]; 1.93 + }); 1.94 + 1.95 + // Allow outer code to add custom headers or remove something 1.96 + if (preprocess) preprocess(loweredHeaders); 1.97 + 1.98 + // Transform object into kv pairs 1.99 + var len = 2, 1.100 + pairs = Object.keys(loweredHeaders).filter(function(key) { 1.101 + var lkey = key.toLowerCase(); 1.102 + return lkey !== 'connection' && lkey !== 'keep-alive' && 1.103 + lkey !== 'proxy-connection' && lkey !== 'transfer-encoding'; 1.104 + }).map(function(key) { 1.105 + var klen = Buffer.byteLength(key), 1.106 + value = stringify(loweredHeaders[key]), 1.107 + vlen = Buffer.byteLength(value); 1.108 + 1.109 + len += 4 + klen + vlen; 1.110 + return [klen, key, vlen, value]; 1.111 + }), 1.112 + result = new Buffer(len); 1.113 + 1.114 + result.writeUInt16BE(pairs.length, 0, true); 1.115 + 1.116 + var offset = 2; 1.117 + pairs.forEach(function(pair) { 1.118 + // Write key length 1.119 + result.writeUInt16BE(pair[0], offset, true); 1.120 + // Write key 1.121 + result.write(pair[1], offset + 2); 1.122 + 1.123 + offset += pair[0] + 2; 1.124 + 1.125 + // Write value length 1.126 + result.writeUInt16BE(pair[2], offset, true); 1.127 + // Write value 1.128 + result.write(pair[3], offset + 2); 1.129 + 1.130 + offset += pair[2] + 2; 1.131 + }); 1.132 + 1.133 + return result; 1.134 +}; 1.135 + 1.136 +Framer.prototype._synFrame = function _synFrame(type, id, assoc, priority, dict, 1.137 + callback) { 1.138 + // Compress headers 1.139 + this.deflate(dict, function (err, chunks, size) { 1.140 + if (err) return callback(err); 1.141 + 1.142 + var offset = type === 'SYN_STREAM' ? 18 : 14, 1.143 + total = (type === 'SYN_STREAM' ? 10 : 6) + size, 1.144 + frame = new Buffer(offset + size);; 1.145 + 1.146 + frame.writeUInt16BE(0x8002, 0, true); // Control + Version 1.147 + frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type 1.148 + frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support 1.149 + frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID 1.150 + 1.151 + if (type === 'SYN_STREAM') { 1.152 + frame[4] = 2; 1.153 + frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID 1.154 + } 1.155 + 1.156 + frame.writeUInt8(priority & 0x3, 16, true); // Priority 1.157 + 1.158 + for (var i = 0; i < chunks.length; i++) { 1.159 + chunks[i].copy(frame, offset); 1.160 + offset += chunks[i].length; 1.161 + } 1.162 + 1.163 + callback(null, frame); 1.164 + }); 1.165 +}; 1.166 + 1.167 +// 1.168 +// ### function replyFrame (id, code, reason, headers, callback) 1.169 +// #### @id {Number} Stream ID 1.170 +// #### @code {Number} HTTP Status Code 1.171 +// #### @reason {String} (optional) 1.172 +// #### @headers {Object|Array} (optional) HTTP headers 1.173 +// #### @callback {Function} Continuation function 1.174 +// Sends SYN_REPLY frame 1.175 +// 1.176 +Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers, 1.177 + callback) { 1.178 + var dict = headersToDict(headers, function(headers) { 1.179 + headers.status = code + ' ' + reason; 1.180 + headers.version = 'HTTP/1.1'; 1.181 + }); 1.182 + 1.183 + this._synFrame('SYN_REPLY', id, null, 0, dict, callback); 1.184 +}; 1.185 + 1.186 +// 1.187 +// ### function streamFrame (id, assoc, headers, callback) 1.188 +// #### @id {Number} stream id 1.189 +// #### @assoc {Number} associated stream id 1.190 +// #### @meta {Object} meta headers ( method, scheme, url, version ) 1.191 +// #### @headers {Object} stream headers 1.192 +// #### @callback {Function} continuation callback 1.193 +// Create SYN_STREAM frame 1.194 +// (needed for server push and testing) 1.195 +// 1.196 +Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers, 1.197 + callback) { 1.198 + var dict = headersToDict(headers, function(headers) { 1.199 + headers.status = 200; 1.200 + headers.version = 'HTTP/1.1'; 1.201 + headers.url = meta.url; 1.202 + }); 1.203 + 1.204 + this._synFrame('SYN_STREAM', id, assoc, meta.priority, dict, callback); 1.205 +}; 1.206 + 1.207 +// 1.208 +// ### function dataFrame (id, fin, data) 1.209 +// #### @id {Number} Stream id 1.210 +// #### @fin {Bool} Is this data frame last frame 1.211 +// #### @data {Buffer} Response data 1.212 +// Sends DATA frame 1.213 +// 1.214 +Framer.prototype.dataFrame = function dataFrame(id, fin, data) { 1.215 + if (!fin && !data.length) return []; 1.216 + 1.217 + var frame = new Buffer(8 + data.length); 1.218 + 1.219 + frame.writeUInt32BE(id & 0x7fffffff, 0, true); 1.220 + frame.writeUInt32BE(data.length & 0x00ffffff, 4, true); 1.221 + frame.writeUInt8(fin ? 0x01 : 0x0, 4, true); 1.222 + 1.223 + if (data.length) data.copy(frame, 8); 1.224 + 1.225 + return frame; 1.226 +}; 1.227 + 1.228 +// 1.229 +// ### function pingFrame (id) 1.230 +// #### @id {Buffer} Ping ID 1.231 +// Sends PING frame 1.232 +// 1.233 +Framer.prototype.pingFrame = function pingFrame(id) { 1.234 + var header = new Buffer(12); 1.235 + 1.236 + header.writeUInt32BE(0x80020006, 0, true); // Version and type 1.237 + header.writeUInt32BE(0x00000004, 4, true); // Length 1.238 + id.copy(header, 8, 0, 4); // ID 1.239 + 1.240 + return header; 1.241 +}; 1.242 + 1.243 +// 1.244 +// ### function rstFrame (id, code) 1.245 +// #### @id {Number} Stream ID 1.246 +// #### @code {NUmber} RST Code 1.247 +// Sends PING frame 1.248 +// 1.249 +Framer.prototype.rstFrame = function rstFrame(id, code) { 1.250 + var header; 1.251 + 1.252 + if (!(header = Framer.rstCache[code])) { 1.253 + header = new Buffer(16); 1.254 + 1.255 + header.writeUInt32BE(0x80020003, 0, true); // Version and type 1.256 + header.writeUInt32BE(0x00000008, 4, true); // Length 1.257 + header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID 1.258 + header.writeUInt32BE(code, 12, true); // Status Code 1.259 + 1.260 + Framer.rstCache[code] = header; 1.261 + } 1.262 + 1.263 + return header; 1.264 +}; 1.265 +Framer.rstCache = {}; 1.266 + 1.267 +// 1.268 +// ### function settingsFrame (options) 1.269 +// #### @options {Object} settings frame options 1.270 +// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS 1.271 +// 1.272 +Framer.prototype.settingsFrame = function settingsFrame(options) { 1.273 + var settings; 1.274 + 1.275 + if (!(settings = Framer.settingsCache[options.maxStreams])) { 1.276 + settings = new Buffer(20); 1.277 + 1.278 + settings.writeUInt32BE(0x80020004, 0, true); // Version and type 1.279 + settings.writeUInt32BE(0x0000000C, 4, true); // length 1.280 + settings.writeUInt32BE(0x00000001, 8, true); // Count of entries 1.281 + settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag 1.282 + settings.writeUInt32BE(options.maxStreams, 16, true); 1.283 + 1.284 + Framer.settingsCache[options.maxStreams] = settings; 1.285 + } 1.286 + 1.287 + return settings; 1.288 +}; 1.289 +Framer.settingsCache = {};