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