| 'use strict' | 
|   | 
| var util = require('util') | 
| var transport = require('../spdy-transport') | 
|   | 
| var debug = { | 
|   server: require('debug')('spdy:connection:server'), | 
|   client: require('debug')('spdy:connection:client') | 
| } | 
| var EventEmitter = require('events').EventEmitter | 
|   | 
| var Stream = transport.Stream | 
|   | 
| function Connection (socket, options) { | 
|   EventEmitter.call(this) | 
|   | 
|   var state = {} | 
|   this._spdyState = state | 
|   | 
|   // NOTE: There's a big trick here. Connection is used as a `this` argument | 
|   // to the wrapped `connection` event listener. | 
|   // socket end doesn't necessarly mean connection drop | 
|   this.httpAllowHalfOpen = true | 
|   | 
|   state.timeout = new transport.utils.Timeout(this) | 
|   | 
|   // Protocol info | 
|   state.protocol = transport.protocol[options.protocol] | 
|   state.version = null | 
|   state.constants = state.protocol.constants | 
|   state.pair = null | 
|   state.isServer = options.isServer | 
|   | 
|   // Root of priority tree (i.e. stream id = 0) | 
|   state.priorityRoot = new transport.Priority({ | 
|     defaultWeight: state.constants.DEFAULT_WEIGHT, | 
|     maxCount: transport.protocol.base.constants.MAX_PRIORITY_STREAMS | 
|   }) | 
|   | 
|   // Defaults | 
|   state.maxStreams = options.maxStreams || | 
|                      state.constants.MAX_CONCURRENT_STREAMS | 
|   | 
|   state.autoSpdy31 = options.protocol.name !== 'h2' && options.autoSpdy31 | 
|   state.acceptPush = options.acceptPush === undefined | 
|     ? !state.isServer | 
|     : options.acceptPush | 
|   | 
|   if (options.maxChunk === false) { state.maxChunk = Infinity } else if (options.maxChunk === undefined) { state.maxChunk = transport.protocol.base.constants.DEFAULT_MAX_CHUNK } else { | 
|     state.maxChunk = options.maxChunk | 
|   } | 
|   | 
|   // Connection-level flow control | 
|   var windowSize = options.windowSize || 1 << 20 | 
|   state.window = new transport.Window({ | 
|     id: 0, | 
|     isServer: state.isServer, | 
|     recv: { | 
|       size: state.constants.DEFAULT_WINDOW, | 
|       max: state.constants.MAX_INITIAL_WINDOW_SIZE | 
|     }, | 
|     send: { | 
|       size: state.constants.DEFAULT_WINDOW, | 
|       max: state.constants.MAX_INITIAL_WINDOW_SIZE | 
|     } | 
|   }) | 
|   | 
|   // It starts with DEFAULT_WINDOW, update must be sent to change it on client | 
|   state.window.recv.setMax(windowSize) | 
|   | 
|   // Boilerplate for Stream constructor | 
|   state.streamWindow = new transport.Window({ | 
|     id: -1, | 
|     isServer: state.isServer, | 
|     recv: { | 
|       size: windowSize, | 
|       max: state.constants.MAX_INITIAL_WINDOW_SIZE | 
|     }, | 
|     send: { | 
|       size: state.constants.DEFAULT_WINDOW, | 
|       max: state.constants.MAX_INITIAL_WINDOW_SIZE | 
|     } | 
|   }) | 
|   | 
|   // Various state info | 
|   state.pool = state.protocol.compressionPool.create(options.headerCompression) | 
|   state.counters = { | 
|     push: 0, | 
|     stream: 0 | 
|   } | 
|   | 
|   // Init streams list | 
|   state.stream = { | 
|     map: {}, | 
|     count: 0, | 
|     nextId: state.isServer ? 2 : 1, | 
|     lastId: { | 
|       both: 0, | 
|       received: 0 | 
|     } | 
|   } | 
|   state.ping = { | 
|     nextId: state.isServer ? 2 : 1, | 
|     map: {} | 
|   } | 
|   state.goaway = false | 
|   | 
|   // Debug | 
|   state.debug = state.isServer ? debug.server : debug.client | 
|   | 
|   // X-Forwarded feature | 
|   state.xForward = null | 
|   | 
|   // Create parser and hole for framer | 
|   state.parser = state.protocol.parser.create({ | 
|     // NOTE: needed to distinguish ping from ping ACK in SPDY | 
|     isServer: state.isServer, | 
|     window: state.window | 
|   }) | 
|   state.framer = state.protocol.framer.create({ | 
|     window: state.window, | 
|     timeout: state.timeout | 
|   }) | 
|   | 
|   // SPDY has PUSH enabled on servers | 
|   if (state.protocol.name === 'spdy') { | 
|     state.framer.enablePush(state.isServer) | 
|   } | 
|   | 
|   if (!state.isServer) { state.parser.skipPreface() } | 
|   | 
|   this.socket = socket | 
|   | 
|   this._init() | 
| } | 
| util.inherits(Connection, EventEmitter) | 
| exports.Connection = Connection | 
|   | 
| Connection.create = function create (socket, options) { | 
|   return new Connection(socket, options) | 
| } | 
|   | 
| Connection.prototype._init = function init () { | 
|   var self = this | 
|   var state = this._spdyState | 
|   var pool = state.pool | 
|   | 
|   // Initialize session window | 
|   state.window.recv.on('drain', function () { | 
|     self._onSessionWindowDrain() | 
|   }) | 
|   | 
|   // Initialize parser | 
|   state.parser.on('data', function (frame) { | 
|     self._handleFrame(frame) | 
|   }) | 
|   state.parser.once('version', function (version) { | 
|     self._onVersion(version) | 
|   }) | 
|   | 
|   // Propagate parser errors | 
|   state.parser.on('error', function (err) { | 
|     self._onParserError(err) | 
|   }) | 
|   | 
|   // Propagate framer errors | 
|   state.framer.on('error', function (err) { | 
|     self.emit('error', err) | 
|   }) | 
|   | 
|   this.socket.pipe(state.parser) | 
|   state.framer.pipe(this.socket) | 
|   | 
|   // Allow high-level api to catch socket errors | 
|   this.socket.on('error', function onSocketError (e) { | 
|     self.emit('error', e) | 
|   }) | 
|   | 
|   this.socket.once('close', function onclose (hadError) { | 
|     var err | 
|     if (hadError) { | 
|       err = new Error('socket hang up') | 
|       err.code = 'ECONNRESET' | 
|     } | 
|   | 
|     self.destroyStreams(err) | 
|     self.emit('close') | 
|   | 
|     if (state.pair) { | 
|       pool.put(state.pair) | 
|     } | 
|   | 
|     state.framer.resume() | 
|   }) | 
|   | 
|   // Reset timeout on close | 
|   this.once('close', function () { | 
|     self.setTimeout(0) | 
|   }) | 
|   | 
|   function _onWindowOverflow () { | 
|     self._onWindowOverflow() | 
|   } | 
|   | 
|   state.window.recv.on('overflow', _onWindowOverflow) | 
|   state.window.send.on('overflow', _onWindowOverflow) | 
|   | 
|   // Do not allow half-open connections | 
|   this.socket.allowHalfOpen = false | 
| } | 
|   | 
| Connection.prototype._onVersion = function _onVersion (version) { | 
|   var state = this._spdyState | 
|   var prev = state.version | 
|   var parser = state.parser | 
|   var framer = state.framer | 
|   var pool = state.pool | 
|   | 
|   state.version = version | 
|   state.debug('id=0 version=%d', version) | 
|   | 
|   // Ignore transition to 3.1 | 
|   if (!prev) { | 
|     state.pair = pool.get(version) | 
|     parser.setCompression(state.pair) | 
|     framer.setCompression(state.pair) | 
|   } | 
|   framer.setVersion(version) | 
|   | 
|   if (!state.isServer) { | 
|     framer.prefaceFrame() | 
|     if (state.xForward !== null) { | 
|       framer.xForwardedFor({ host: state.xForward }) | 
|     } | 
|   } | 
|   | 
|   // Send preface+settings frame (once) | 
|   framer.settingsFrame({ | 
|     max_header_list_size: state.constants.DEFAULT_MAX_HEADER_LIST_SIZE, | 
|     max_concurrent_streams: state.maxStreams, | 
|     enable_push: state.acceptPush ? 1 : 0, | 
|     initial_window_size: state.window.recv.max | 
|   }) | 
|   | 
|   // Update session window | 
|   if (state.version >= 3.1 || (state.isServer && state.autoSpdy31)) { this._onSessionWindowDrain() } | 
|   | 
|   this.emit('version', version) | 
| } | 
|   | 
| Connection.prototype._onParserError = function _onParserError (err) { | 
|   var state = this._spdyState | 
|   | 
|   // Prevent further errors | 
|   state.parser.kill() | 
|   | 
|   // Send GOAWAY | 
|   if (err instanceof transport.protocol.base.utils.ProtocolError) { | 
|     this._goaway({ | 
|       lastId: state.stream.lastId.both, | 
|       code: err.code, | 
|       extra: err.message, | 
|       send: true | 
|     }) | 
|   } | 
|   | 
|   this.emit('error', err) | 
| } | 
|   | 
| Connection.prototype._handleFrame = function _handleFrame (frame) { | 
|   var state = this._spdyState | 
|   | 
|   state.debug('id=0 frame', frame) | 
|   state.timeout.reset() | 
|   | 
|   // For testing purposes | 
|   this.emit('frame', frame) | 
|   | 
|   var stream | 
|   | 
|   // Session window update | 
|   if (frame.type === 'WINDOW_UPDATE' && frame.id === 0) { | 
|     if (state.version < 3.1 && state.autoSpdy31) { | 
|       state.debug('id=0 switch version to 3.1') | 
|       state.version = 3.1 | 
|       this.emit('version', 3.1) | 
|     } | 
|     state.window.send.update(frame.delta) | 
|     return | 
|   } | 
|   | 
|   if (state.isServer && frame.type === 'PUSH_PROMISE') { | 
|     state.debug('id=0 server PUSH_PROMISE') | 
|     this._goaway({ | 
|       lastId: state.stream.lastId.both, | 
|       code: 'PROTOCOL_ERROR', | 
|       send: true | 
|     }) | 
|     return | 
|   } | 
|   | 
|   if (!stream && frame.id !== undefined) { | 
|     // Load created one | 
|     stream = state.stream.map[frame.id] | 
|   | 
|     // Fail if not found | 
|     if (!stream && | 
|         frame.type !== 'HEADERS' && | 
|         frame.type !== 'PRIORITY' && | 
|         frame.type !== 'RST') { | 
|       // Other side should destroy the stream upon receiving GOAWAY | 
|       if (this._isGoaway(frame.id)) { return } | 
|   | 
|       state.debug('id=0 stream=%d not found', frame.id) | 
|       state.framer.rstFrame({ id: frame.id, code: 'INVALID_STREAM' }) | 
|       return | 
|     } | 
|   } | 
|   | 
|   // Create new stream | 
|   if (!stream && frame.type === 'HEADERS') { | 
|     this._handleHeaders(frame) | 
|     return | 
|   } | 
|   | 
|   if (stream) { | 
|     stream._handleFrame(frame) | 
|   } else if (frame.type === 'SETTINGS') { | 
|     this._handleSettings(frame.settings) | 
|   } else if (frame.type === 'ACK_SETTINGS') { | 
|     // TODO(indutny): handle it one day | 
|   } else if (frame.type === 'PING') { | 
|     this._handlePing(frame) | 
|   } else if (frame.type === 'GOAWAY') { | 
|     this._handleGoaway(frame) | 
|   } else if (frame.type === 'X_FORWARDED_FOR') { | 
|     // Set X-Forwarded-For only once | 
|     if (state.xForward === null) { | 
|       state.xForward = frame.host | 
|     } | 
|   } else if (frame.type === 'PRIORITY') { | 
|     // TODO(indutny): handle this | 
|   } else { | 
|     state.debug('id=0 unknown frame type: %s', frame.type) | 
|   } | 
| } | 
|   | 
| Connection.prototype._onWindowOverflow = function _onWindowOverflow () { | 
|   var state = this._spdyState | 
|   state.debug('id=0 window overflow') | 
|   this._goaway({ | 
|     lastId: state.stream.lastId.both, | 
|     code: 'FLOW_CONTROL_ERROR', | 
|     send: true | 
|   }) | 
| } | 
|   | 
| Connection.prototype._isGoaway = function _isGoaway (id) { | 
|   var state = this._spdyState | 
|   if (state.goaway !== false && state.goaway < id) { return true } | 
|   return false | 
| } | 
|   | 
| Connection.prototype._getId = function _getId () { | 
|   var state = this._spdyState | 
|   | 
|   var id = state.stream.nextId | 
|   state.stream.nextId += 2 | 
|   return id | 
| } | 
|   | 
| Connection.prototype._createStream = function _createStream (uri) { | 
|   var state = this._spdyState | 
|   var id = uri.id | 
|   if (id === undefined) { id = this._getId() } | 
|   | 
|   var isGoaway = this._isGoaway(id) | 
|   | 
|   if (uri.push && !state.acceptPush) { | 
|     state.debug('id=0 push disabled promisedId=%d', id) | 
|   | 
|     // Fatal error | 
|     this._goaway({ | 
|       lastId: state.stream.lastId.both, | 
|       code: 'PROTOCOL_ERROR', | 
|       send: true | 
|     }) | 
|     isGoaway = true | 
|   } | 
|   | 
|   var stream = new Stream(this, { | 
|     id: id, | 
|     request: uri.request !== false, | 
|     method: uri.method, | 
|     path: uri.path, | 
|     host: uri.host, | 
|     priority: uri.priority, | 
|     headers: uri.headers, | 
|     parent: uri.parent, | 
|     readable: !isGoaway && uri.readable, | 
|     writable: !isGoaway && uri.writable | 
|   }) | 
|   var self = this | 
|   | 
|   // Just an empty stream for API consistency | 
|   if (isGoaway) { | 
|     return stream | 
|   } | 
|   | 
|   state.stream.lastId.both = Math.max(state.stream.lastId.both, id) | 
|   | 
|   state.debug('id=0 add stream=%d', stream.id) | 
|   state.stream.map[stream.id] = stream | 
|   state.stream.count++ | 
|   state.counters.stream++ | 
|   if (stream.parent !== null) { | 
|     state.counters.push++ | 
|   } | 
|   | 
|   stream.once('close', function () { | 
|     self._removeStream(stream) | 
|   }) | 
|   | 
|   return stream | 
| } | 
|   | 
| Connection.prototype._handleHeaders = function _handleHeaders (frame) { | 
|   var state = this._spdyState | 
|   | 
|   // Must be HEADERS frame after stream close | 
|   if (frame.id <= state.stream.lastId.received) { return } | 
|   | 
|   // Someone is using our ids! | 
|   if ((frame.id + state.stream.nextId) % 2 === 0) { | 
|     state.framer.rstFrame({ id: frame.id, code: 'PROTOCOL_ERROR' }) | 
|     return | 
|   } | 
|   | 
|   var stream = this._createStream({ | 
|     id: frame.id, | 
|     request: false, | 
|     method: frame.headers[':method'], | 
|     path: frame.headers[':path'], | 
|     host: frame.headers[':authority'], | 
|     priority: frame.priority, | 
|     headers: frame.headers, | 
|     writable: frame.writable | 
|   }) | 
|   | 
|   // GOAWAY | 
|   if (this._isGoaway(stream.id)) { | 
|     return | 
|   } | 
|   | 
|   state.stream.lastId.received = Math.max( | 
|     state.stream.lastId.received, | 
|     stream.id | 
|   ) | 
|   | 
|   // TODO(indutny) handle stream limit | 
|   if (!this.emit('stream', stream)) { | 
|     // No listeners was set - abort the stream | 
|     stream.abort() | 
|     return | 
|   } | 
|   | 
|   // Create fake frame to simulate end of the data | 
|   if (frame.fin) { | 
|     stream._handleFrame({ type: 'FIN', fin: true }) | 
|   } | 
|   | 
|   return stream | 
| } | 
|   | 
| Connection.prototype._onSessionWindowDrain = function _onSessionWindowDrain () { | 
|   var state = this._spdyState | 
|   if (state.version < 3.1 && !(state.isServer && state.autoSpdy31)) { | 
|     return | 
|   } | 
|   | 
|   var delta = state.window.recv.getDelta() | 
|   if (delta === 0) { | 
|     return | 
|   } | 
|   | 
|   state.debug('id=0 session window drain, update by %d', delta) | 
|   | 
|   state.framer.windowUpdateFrame({ | 
|     id: 0, | 
|     delta: delta | 
|   }) | 
|   state.window.recv.update(delta) | 
| } | 
|   | 
| Connection.prototype.start = function start (version) { | 
|   this._spdyState.parser.setVersion(version) | 
| } | 
|   | 
| // Mostly for testing | 
| Connection.prototype.getVersion = function getVersion () { | 
|   return this._spdyState.version | 
| } | 
|   | 
| Connection.prototype._handleSettings = function _handleSettings (settings) { | 
|   var state = this._spdyState | 
|   | 
|   state.framer.ackSettingsFrame() | 
|   | 
|   this._setDefaultWindow(settings) | 
|   if (settings.max_frame_size) { state.framer.setMaxFrameSize(settings.max_frame_size) } | 
|   | 
|   // TODO(indutny): handle max_header_list_size | 
|   if (settings.header_table_size) { | 
|     try { | 
|       state.pair.compress.updateTableSize(settings.header_table_size) | 
|     } catch (e) { | 
|       this._goaway({ | 
|         lastId: 0, | 
|         code: 'PROTOCOL_ERROR', | 
|         send: true | 
|       }) | 
|       return | 
|     } | 
|   } | 
|   | 
|   // HTTP2 clients needs to enable PUSH streams explicitly | 
|   if (state.protocol.name !== 'spdy') { | 
|     if (settings.enable_push === undefined) { | 
|       state.framer.enablePush(state.isServer) | 
|     } else { | 
|       state.framer.enablePush(settings.enable_push === 1) | 
|     } | 
|   } | 
|   | 
|   // TODO(indutny): handle max_concurrent_streams | 
| } | 
|   | 
| Connection.prototype._setDefaultWindow = function _setDefaultWindow (settings) { | 
|   if (settings.initial_window_size === undefined) { | 
|     return | 
|   } | 
|   | 
|   var state = this._spdyState | 
|   | 
|   // Update defaults | 
|   var window = state.streamWindow | 
|   window.send.setMax(settings.initial_window_size) | 
|   | 
|   // Update existing streams | 
|   Object.keys(state.stream.map).forEach(function (id) { | 
|     var stream = state.stream.map[id] | 
|     var window = stream._spdyState.window | 
|   | 
|     window.send.updateMax(settings.initial_window_size) | 
|   }) | 
| } | 
|   | 
| Connection.prototype._handlePing = function handlePing (frame) { | 
|   var self = this | 
|   var state = this._spdyState | 
|   | 
|   // Handle incoming PING | 
|   if (!frame.ack) { | 
|     state.framer.pingFrame({ | 
|       opaque: frame.opaque, | 
|       ack: true | 
|     }) | 
|   | 
|     self.emit('ping', frame.opaque) | 
|     return | 
|   } | 
|   | 
|   // Handle reply PING | 
|   var hex = frame.opaque.toString('hex') | 
|   if (!state.ping.map[hex]) { | 
|     return | 
|   } | 
|   var ping = state.ping.map[hex] | 
|   delete state.ping.map[hex] | 
|   | 
|   if (ping.cb) { | 
|     ping.cb(null) | 
|   } | 
| } | 
|   | 
| Connection.prototype._handleGoaway = function handleGoaway (frame) { | 
|   this._goaway({ | 
|     lastId: frame.lastId, | 
|     code: frame.code, | 
|     send: false | 
|   }) | 
| } | 
|   | 
| Connection.prototype.ping = function ping (callback) { | 
|   var state = this._spdyState | 
|   | 
|   // HTTP2 is using 8-byte opaque | 
|   var opaque = Buffer.alloc(state.constants.PING_OPAQUE_SIZE) | 
|   opaque.fill(0) | 
|   opaque.writeUInt32BE(state.ping.nextId, opaque.length - 4) | 
|   state.ping.nextId += 2 | 
|   | 
|   state.ping.map[opaque.toString('hex')] = { cb: callback } | 
|   state.framer.pingFrame({ | 
|     opaque: opaque, | 
|     ack: false | 
|   }) | 
| } | 
|   | 
| Connection.prototype.getCounter = function getCounter (name) { | 
|   return this._spdyState.counters[name] | 
| } | 
|   | 
| Connection.prototype.reserveStream = function reserveStream (uri, callback) { | 
|   var stream = this._createStream(uri) | 
|   | 
|   // GOAWAY | 
|   if (this._isGoaway(stream.id)) { | 
|     var err = new Error('Can\'t send request after GOAWAY') | 
|     process.nextTick(function () { | 
|       if (callback) { callback(err) } else { | 
|         stream.emit('error', err) | 
|       } | 
|     }) | 
|     return stream | 
|   } | 
|   | 
|   if (callback) { | 
|     process.nextTick(function () { | 
|       callback(null, stream) | 
|     }) | 
|   } | 
|   | 
|   return stream | 
| } | 
|   | 
| Connection.prototype.request = function request (uri, callback) { | 
|   var stream = this.reserveStream(uri, function (err) { | 
|     if (err) { | 
|       if (callback) { | 
|         callback(err) | 
|       } else { | 
|         stream.emit('error', err) | 
|       } | 
|       return | 
|     } | 
|   | 
|     if (stream._wasSent()) { | 
|       if (callback) { | 
|         callback(null, stream) | 
|       } | 
|       return | 
|     } | 
|   | 
|     stream.send(function (err) { | 
|       if (err) { | 
|         if (callback) { return callback(err) } else { return stream.emit('error', err) } | 
|       } | 
|   | 
|       if (callback) { | 
|         callback(null, stream) | 
|       } | 
|     }) | 
|   }) | 
|   | 
|   return stream | 
| } | 
|   | 
| Connection.prototype._removeStream = function _removeStream (stream) { | 
|   var state = this._spdyState | 
|   | 
|   state.debug('id=0 remove stream=%d', stream.id) | 
|   delete state.stream.map[stream.id] | 
|   state.stream.count-- | 
|   | 
|   if (state.stream.count === 0) { | 
|     this.emit('_streamDrain') | 
|   } | 
| } | 
|   | 
| Connection.prototype._goaway = function _goaway (params) { | 
|   var state = this._spdyState | 
|   var self = this | 
|   | 
|   state.goaway = params.lastId | 
|   state.debug('id=0 goaway from=%d', state.goaway) | 
|   | 
|   Object.keys(state.stream.map).forEach(function (id) { | 
|     var stream = state.stream.map[id] | 
|   | 
|     // Abort every stream started after GOAWAY | 
|     if (stream.id <= params.lastId) { | 
|       return | 
|     } | 
|   | 
|     stream.abort() | 
|     stream.emit('error', new Error('New stream after GOAWAY')) | 
|   }) | 
|   | 
|   function finish () { | 
|     // Destroy socket if there are no streams | 
|     if (state.stream.count === 0 || params.code !== 'OK') { | 
|       // No further frames should be processed | 
|       state.parser.kill() | 
|   | 
|       process.nextTick(function () { | 
|         var err = new Error('Fatal error: ' + params.code) | 
|         self._onStreamDrain(err) | 
|       }) | 
|       return | 
|     } | 
|   | 
|     self.on('_streamDrain', self._onStreamDrain) | 
|   } | 
|   | 
|   if (params.send) { | 
|     // Make sure that GOAWAY frame is sent before dumping framer | 
|     state.framer.goawayFrame({ | 
|       lastId: params.lastId, | 
|       code: params.code, | 
|       extra: params.extra | 
|     }, finish) | 
|   } else { | 
|     finish() | 
|   } | 
| } | 
|   | 
| Connection.prototype._onStreamDrain = function _onStreamDrain (error) { | 
|   var state = this._spdyState | 
|   | 
|   state.debug('id=0 _onStreamDrain') | 
|   | 
|   state.framer.dump() | 
|   state.framer.unpipe(this.socket) | 
|   state.framer.resume() | 
|   | 
|   if (this.socket.destroySoon) { | 
|     this.socket.destroySoon() | 
|   } | 
|   this.emit('close', error) | 
| } | 
|   | 
| Connection.prototype.end = function end (callback) { | 
|   var state = this._spdyState | 
|   | 
|   if (callback) { | 
|     this.once('close', callback) | 
|   } | 
|   this._goaway({ | 
|     lastId: state.stream.lastId.both, | 
|     code: 'OK', | 
|     send: true | 
|   }) | 
| } | 
|   | 
| Connection.prototype.destroyStreams = function destroyStreams (err) { | 
|   var state = this._spdyState | 
|   Object.keys(state.stream.map).forEach(function (id) { | 
|     var stream = state.stream.map[id] | 
|   | 
|     stream.destroy() | 
|     if (err) { | 
|       stream.emit('error', err) | 
|     } | 
|   }) | 
| } | 
|   | 
| Connection.prototype.isServer = function isServer () { | 
|   return this._spdyState.isServer | 
| } | 
|   | 
| Connection.prototype.getXForwardedFor = function getXForwardFor () { | 
|   return this._spdyState.xForward | 
| } | 
|   | 
| Connection.prototype.sendXForwardedFor = function sendXForwardedFor (host) { | 
|   var state = this._spdyState | 
|   if (state.version !== null) { | 
|     state.framer.xForwardedFor({ host: host }) | 
|   } else { | 
|     state.xForward = host | 
|   } | 
| } | 
|   | 
| Connection.prototype.pushPromise = function pushPromise (parent, uri, callback) { | 
|   var state = this._spdyState | 
|   | 
|   var stream = this._createStream({ | 
|     request: false, | 
|     parent: parent, | 
|     method: uri.method, | 
|     path: uri.path, | 
|     host: uri.host, | 
|     priority: uri.priority, | 
|     headers: uri.headers, | 
|     readable: false | 
|   }) | 
|   | 
|   var err | 
|   | 
|   // TODO(indutny): deduplicate this logic somehow | 
|   if (this._isGoaway(stream.id)) { | 
|     err = new Error('Can\'t send PUSH_PROMISE after GOAWAY') | 
|   | 
|     process.nextTick(function () { | 
|       if (callback) { | 
|         callback(err) | 
|       } else { | 
|         stream.emit('error', err) | 
|       } | 
|     }) | 
|     return stream | 
|   } | 
|   | 
|   if (uri.push && !state.acceptPush) { | 
|     err = new Error( | 
|       'Can\'t send PUSH_PROMISE, other side won\'t accept it') | 
|     process.nextTick(function () { | 
|       if (callback) { callback(err) } else { | 
|         stream.emit('error', err) | 
|       } | 
|     }) | 
|     return stream | 
|   } | 
|   | 
|   stream._sendPush(uri.status, uri.response, function (err) { | 
|     if (!callback) { | 
|       if (err) { | 
|         stream.emit('error', err) | 
|       } | 
|       return | 
|     } | 
|   | 
|     if (err) { return callback(err) } | 
|     callback(null, stream) | 
|   }) | 
|   | 
|   return stream | 
| } | 
|   | 
| Connection.prototype.setTimeout = function setTimeout (delay, callback) { | 
|   var state = this._spdyState | 
|   | 
|   state.timeout.set(delay, callback) | 
| } |