| 'use strict' | 
|   | 
| var transport = require('../../../spdy-transport') | 
| var constants = require('./').constants | 
| var base = transport.protocol.base | 
| var utils = base.utils | 
|   | 
| var assert = require('assert') | 
| var util = require('util') | 
| var Buffer = require('buffer').Buffer | 
| var WriteBuffer = require('wbuf') | 
|   | 
| var debug = require('debug')('spdy:framer') | 
|   | 
| function Framer (options) { | 
|   base.Framer.call(this, options) | 
| } | 
| util.inherits(Framer, base.Framer) | 
| module.exports = Framer | 
|   | 
| Framer.create = function create (options) { | 
|   return new Framer(options) | 
| } | 
|   | 
| Framer.prototype.setMaxFrameSize = function setMaxFrameSize (size) { | 
|   // http2-only | 
| } | 
|   | 
| Framer.prototype.headersToDict = function headersToDict (headers, | 
|   preprocess, | 
|   callback) { | 
|   function stringify (value) { | 
|     if (value !== undefined) { | 
|       if (Array.isArray(value)) { | 
|         return value.join('\x00') | 
|       } else if (typeof value === 'string') { | 
|         return value | 
|       } else { | 
|         return value.toString() | 
|       } | 
|     } else { | 
|       return '' | 
|     } | 
|   } | 
|   | 
|   // Lower case of all headers keys | 
|   var loweredHeaders = {} | 
|   Object.keys(headers || {}).map(function (key) { | 
|     loweredHeaders[key.toLowerCase()] = headers[key] | 
|   }) | 
|   | 
|   // Allow outer code to add custom headers or remove something | 
|   if (preprocess) { preprocess(loweredHeaders) } | 
|   | 
|   // Transform object into kv pairs | 
|   var size = this.version === 2 ? 2 : 4 | 
|   var len = size | 
|   var pairs = Object.keys(loweredHeaders).filter(function (key) { | 
|     var lkey = key.toLowerCase() | 
|   | 
|     // Will be in `:host` | 
|     if (lkey === 'host' && this.version >= 3) { | 
|       return false | 
|     } | 
|   | 
|     return lkey !== 'connection' && lkey !== 'keep-alive' && | 
|            lkey !== 'proxy-connection' && lkey !== 'transfer-encoding' | 
|   }, this).map(function (key) { | 
|     var klen = Buffer.byteLength(key) | 
|     var value = stringify(loweredHeaders[key]) | 
|     var vlen = Buffer.byteLength(value) | 
|   | 
|     len += size * 2 + klen + vlen | 
|     return [klen, key, vlen, value] | 
|   }) | 
|   | 
|   var block = new WriteBuffer() | 
|   block.reserve(len) | 
|   | 
|   if (this.version === 2) { | 
|     block.writeUInt16BE(pairs.length) | 
|   } else { | 
|     block.writeUInt32BE(pairs.length) | 
|   } | 
|   | 
|   pairs.forEach(function (pair) { | 
|     // Write key length | 
|     if (this.version === 2) { | 
|       block.writeUInt16BE(pair[0]) | 
|     } else { | 
|       block.writeUInt32BE(pair[0]) | 
|     } | 
|   | 
|     // Write key | 
|     block.write(pair[1]) | 
|   | 
|     // Write value length | 
|     if (this.version === 2) { | 
|       block.writeUInt16BE(pair[2]) | 
|     } else { | 
|       block.writeUInt32BE(pair[2]) | 
|     } | 
|     // Write value | 
|     block.write(pair[3]) | 
|   }, this) | 
|   | 
|   assert(this.compress !== null, 'Framer version not initialized') | 
|   this.compress.write(block.render(), callback) | 
| } | 
|   | 
| Framer.prototype._frame = function _frame (frame, body, callback) { | 
|   if (!this.version) { | 
|     this.on('version', function () { | 
|       this._frame(frame, body, callback) | 
|     }) | 
|     return | 
|   } | 
|   | 
|   debug('id=%d type=%s', frame.id, frame.type) | 
|   | 
|   var buffer = new WriteBuffer() | 
|   | 
|   buffer.writeUInt16BE(0x8000 | this.version) | 
|   buffer.writeUInt16BE(constants.frameType[frame.type]) | 
|   buffer.writeUInt8(frame.flags) | 
|   var len = buffer.skip(3) | 
|   | 
|   body(buffer) | 
|   | 
|   var frameSize = buffer.size - constants.FRAME_HEADER_SIZE | 
|   len.writeUInt24BE(frameSize) | 
|   | 
|   var chunks = buffer.render() | 
|   var toWrite = { | 
|     stream: frame.id, | 
|     priority: false, | 
|     chunks: chunks, | 
|     callback: callback | 
|   } | 
|   | 
|   this._resetTimeout() | 
|   this.schedule(toWrite) | 
|   | 
|   return chunks | 
| } | 
|   | 
| Framer.prototype._synFrame = function _synFrame (frame, callback) { | 
|   var self = this | 
|   | 
|   if (!frame.path) { | 
|     throw new Error('`path` is required frame argument') | 
|   } | 
|   | 
|   function preprocess (headers) { | 
|     var method = frame.method || base.constants.DEFAULT_METHOD | 
|     var version = frame.version || 'HTTP/1.1' | 
|     var scheme = frame.scheme || 'https' | 
|     var host = frame.host || | 
|                (frame.headers && frame.headers.host) || | 
|                base.constants.DEFAULT_HOST | 
|   | 
|     if (self.version === 2) { | 
|       headers.method = method | 
|       headers.version = version | 
|       headers.url = frame.path | 
|       headers.scheme = scheme | 
|       headers.host = host | 
|       if (frame.status) { | 
|         headers.status = frame.status | 
|       } | 
|     } else { | 
|       headers[':method'] = method | 
|       headers[':version'] = version | 
|       headers[':path'] = frame.path | 
|       headers[':scheme'] = scheme | 
|       headers[':host'] = host | 
|       if (frame.status) { headers[':status'] = frame.status } | 
|     } | 
|   } | 
|   | 
|   this.headersToDict(frame.headers, preprocess, function (err, chunks) { | 
|     if (err) { | 
|       if (callback) { | 
|         return callback(err) | 
|       } else { | 
|         return self.emit('error', err) | 
|       } | 
|     } | 
|   | 
|     self._frame({ | 
|       type: 'SYN_STREAM', | 
|       id: frame.id, | 
|       flags: frame.fin ? constants.flags.FLAG_FIN : 0 | 
|     }, function (buf) { | 
|       buf.reserve(10) | 
|   | 
|       buf.writeUInt32BE(frame.id & 0x7fffffff) | 
|       buf.writeUInt32BE(frame.associated & 0x7fffffff) | 
|   | 
|       var weight = (frame.priority && frame.priority.weight) || | 
|                    constants.DEFAULT_WEIGHT | 
|   | 
|       // We only have 3 bits for priority in SPDY, try to fit it into this | 
|       var priority = utils.weightToPriority(weight) | 
|       buf.writeUInt8(priority << 5) | 
|   | 
|       // CREDENTIALS slot | 
|       buf.writeUInt8(0) | 
|   | 
|       for (var i = 0; i < chunks.length; i++) { | 
|         buf.copyFrom(chunks[i]) | 
|       } | 
|     }, callback) | 
|   }) | 
| } | 
|   | 
| Framer.prototype.requestFrame = function requestFrame (frame, callback) { | 
|   this._synFrame({ | 
|     id: frame.id, | 
|     fin: frame.fin, | 
|     associated: 0, | 
|     method: frame.method, | 
|     version: frame.version, | 
|     scheme: frame.scheme, | 
|     host: frame.host, | 
|     path: frame.path, | 
|     priority: frame.priority, | 
|     headers: frame.headers | 
|   }, callback) | 
| } | 
|   | 
| Framer.prototype.responseFrame = function responseFrame (frame, callback) { | 
|   var self = this | 
|   | 
|   var reason = frame.reason | 
|   if (!reason) { | 
|     reason = constants.statusReason[frame.status] | 
|   } | 
|   | 
|   function preprocess (headers) { | 
|     if (self.version === 2) { | 
|       headers.status = frame.status + ' ' + reason | 
|       headers.version = 'HTTP/1.1' | 
|     } else { | 
|       headers[':status'] = frame.status + ' ' + reason | 
|       headers[':version'] = 'HTTP/1.1' | 
|     } | 
|   } | 
|   | 
|   this.headersToDict(frame.headers, preprocess, function (err, chunks) { | 
|     if (err) { | 
|       if (callback) { | 
|         return callback(err) | 
|       } else { | 
|         return self.emit('error', err) | 
|       } | 
|     } | 
|   | 
|     self._frame({ | 
|       type: 'SYN_REPLY', | 
|       id: frame.id, | 
|       flags: 0 | 
|     }, function (buf) { | 
|       buf.reserve(self.version === 2 ? 6 : 4) | 
|   | 
|       buf.writeUInt32BE(frame.id & 0x7fffffff) | 
|   | 
|       // Unused data | 
|       if (self.version === 2) { | 
|         buf.writeUInt16BE(0) | 
|       } | 
|   | 
|       for (var i = 0; i < chunks.length; i++) { | 
|         buf.copyFrom(chunks[i]) | 
|       } | 
|     }, callback) | 
|   }) | 
| } | 
|   | 
| Framer.prototype.pushFrame = function pushFrame (frame, callback) { | 
|   var self = this | 
|   | 
|   this._checkPush(function (err) { | 
|     if (err) { return callback(err) } | 
|   | 
|     self._synFrame({ | 
|       id: frame.promisedId, | 
|       associated: frame.id, | 
|       method: frame.method, | 
|       status: frame.status || 200, | 
|       version: frame.version, | 
|       scheme: frame.scheme, | 
|       host: frame.host, | 
|       path: frame.path, | 
|       priority: frame.priority, | 
|   | 
|       // Merge everything together, there is no difference in SPDY protocol | 
|       headers: Object.assign(Object.assign({}, frame.headers), frame.response) | 
|     }, callback) | 
|   }) | 
| } | 
|   | 
| Framer.prototype.headersFrame = function headersFrame (frame, callback) { | 
|   var self = this | 
|   | 
|   this.headersToDict(frame.headers, null, function (err, chunks) { | 
|     if (err) { | 
|       if (callback) { return callback(err) } else { | 
|         return self.emit('error', err) | 
|       } | 
|     } | 
|   | 
|     self._frame({ | 
|       type: 'HEADERS', | 
|       id: frame.id, | 
|       priority: false, | 
|       flags: 0 | 
|     }, function (buf) { | 
|       buf.reserve(4 + (self.version === 2 ? 2 : 0)) | 
|       buf.writeUInt32BE(frame.id & 0x7fffffff) | 
|   | 
|       // Unused data | 
|       if (self.version === 2) { buf.writeUInt16BE(0) } | 
|   | 
|       for (var i = 0; i < chunks.length; i++) { | 
|         buf.copyFrom(chunks[i]) | 
|       } | 
|     }, callback) | 
|   }) | 
| } | 
|   | 
| Framer.prototype.dataFrame = function dataFrame (frame, callback) { | 
|   if (!this.version) { | 
|     return this.on('version', function () { | 
|       this.dataFrame(frame, callback) | 
|     }) | 
|   } | 
|   | 
|   debug('id=%d type=DATA', frame.id) | 
|   | 
|   var buffer = new WriteBuffer() | 
|   buffer.reserve(8 + frame.data.length) | 
|   | 
|   buffer.writeUInt32BE(frame.id & 0x7fffffff) | 
|   buffer.writeUInt8(frame.fin ? 0x01 : 0x0) | 
|   buffer.writeUInt24BE(frame.data.length) | 
|   buffer.copyFrom(frame.data) | 
|   | 
|   var chunks = buffer.render() | 
|   var toWrite = { | 
|     stream: frame.id, | 
|     priority: frame.priority, | 
|     chunks: chunks, | 
|     callback: callback | 
|   } | 
|   | 
|   var self = this | 
|   this._resetTimeout() | 
|   | 
|   var bypass = this.version < 3.1 | 
|   this.window.send.update(-frame.data.length, bypass ? undefined : function () { | 
|     self._resetTimeout() | 
|     self.schedule(toWrite) | 
|   }) | 
|   | 
|   if (bypass) { | 
|     this._resetTimeout() | 
|     this.schedule(toWrite) | 
|   } | 
| } | 
|   | 
| Framer.prototype.pingFrame = function pingFrame (frame, callback) { | 
|   this._frame({ | 
|     type: 'PING', | 
|     id: 0, | 
|     flags: 0 | 
|   }, function (buf, callback) { | 
|     buf.reserve(4) | 
|   | 
|     var opaque = frame.opaque | 
|     buf.writeUInt32BE(opaque.readUInt32BE(opaque.length - 4, true)) | 
|   }, callback) | 
| } | 
|   | 
| Framer.prototype.rstFrame = function rstFrame (frame, callback) { | 
|   this._frame({ | 
|     type: 'RST_STREAM', | 
|     id: frame.id, | 
|     flags: 0 | 
|   }, function (buf) { | 
|     buf.reserve(8) | 
|   | 
|     // Stream ID | 
|     buf.writeUInt32BE(frame.id & 0x7fffffff) | 
|     // Status Code | 
|     buf.writeUInt32BE(constants.error[frame.code]) | 
|   | 
|     // Extra debugging information | 
|     if (frame.extra) { | 
|       buf.write(frame.extra) | 
|     } | 
|   }, callback) | 
| } | 
|   | 
| Framer.prototype.prefaceFrame = function prefaceFrame () { | 
| } | 
|   | 
| Framer.prototype.settingsFrame = function settingsFrame (options, callback) { | 
|   var self = this | 
|   | 
|   var key = this.version + '/' + JSON.stringify(options) | 
|   | 
|   var settings = Framer.settingsCache[key] | 
|   if (settings) { | 
|     debug('cached settings') | 
|     this._resetTimeout() | 
|     this.schedule({ | 
|       stream: 0, | 
|       priority: false, | 
|       chunks: settings, | 
|       callback: callback | 
|     }) | 
|     return | 
|   } | 
|   | 
|   var params = [] | 
|   for (var i = 0; i < constants.settingsIndex.length; i++) { | 
|     var name = constants.settingsIndex[i] | 
|     if (!name) { continue } | 
|   | 
|     // value: Infinity | 
|     if (!isFinite(options[name])) { | 
|       continue | 
|     } | 
|   | 
|     if (options[name] !== undefined) { | 
|       params.push({ key: i, value: options[name] }) | 
|     } | 
|   } | 
|   | 
|   var frame = this._frame({ | 
|     type: 'SETTINGS', | 
|     id: 0, | 
|     flags: 0 | 
|   }, function (buf) { | 
|     buf.reserve(4 + 8 * params.length) | 
|   | 
|     // Count of entries | 
|     buf.writeUInt32BE(params.length) | 
|   | 
|     params.forEach(function (param) { | 
|       var flag = constants.settings.FLAG_SETTINGS_PERSIST_VALUE << 24 | 
|   | 
|       if (self.version === 2) { | 
|         buf.writeUInt32LE(flag | param.key) | 
|       } else { buf.writeUInt32BE(flag | param.key) } | 
|       buf.writeUInt32BE(param.value & 0x7fffffff) | 
|     }) | 
|   }, callback) | 
|   | 
|   Framer.settingsCache[key] = frame | 
| } | 
| Framer.settingsCache = {} | 
|   | 
| Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) { | 
|   if (callback) { | 
|     process.nextTick(callback) | 
|   } | 
| } | 
|   | 
| Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame, | 
|   callback) { | 
|   this._frame({ | 
|     type: 'WINDOW_UPDATE', | 
|     id: frame.id, | 
|     flags: 0 | 
|   }, function (buf) { | 
|     buf.reserve(8) | 
|   | 
|     // ID | 
|     buf.writeUInt32BE(frame.id & 0x7fffffff) | 
|   | 
|     // Delta | 
|     buf.writeInt32BE(frame.delta) | 
|   }, callback) | 
| } | 
|   | 
| Framer.prototype.goawayFrame = function goawayFrame (frame, callback) { | 
|   this._frame({ | 
|     type: 'GOAWAY', | 
|     id: 0, | 
|     flags: 0 | 
|   }, function (buf) { | 
|     buf.reserve(8) | 
|   | 
|     // Last-good-stream-ID | 
|     buf.writeUInt32BE(frame.lastId & 0x7fffffff) | 
|     // Status | 
|     buf.writeUInt32BE(constants.goaway[frame.code]) | 
|   }, callback) | 
| } | 
|   | 
| Framer.prototype.priorityFrame = function priorityFrame (frame, callback) { | 
|   // No such thing in SPDY | 
|   if (callback) { | 
|     process.nextTick(callback) | 
|   } | 
| } | 
|   | 
| Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) { | 
|   this._frame({ | 
|     type: 'X_FORWARDED_FOR', | 
|     id: 0, | 
|     flags: 0 | 
|   }, function (buf) { | 
|     buf.writeUInt32BE(Buffer.byteLength(frame.host)) | 
|     buf.write(frame.host) | 
|   }, callback) | 
| } |