code:
'use strict'

var parser = exports

var transport = require('../../../spdy-transport')
var base = transport.protocol.base
var utils = base.utils
var constants = require('./').constants

var assert = require('assert')
var util = require('util')

function Parser (options) {
  base.Parser.call(this, options)

  this.isServer = options.isServer

  this.waiting = constants.PREFACE_SIZE
  this.state = 'preface'
  this.pendingHeader = null

  // Header Block queue
  this._lastHeaderBlock = null
  this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
  this.maxHeaderListSize = constants.DEFAULT_MAX_HEADER_LIST_SIZE
}
util.inherits(Parser, base.Parser)

parser.create = function create (options) {
  return new Parser(options)
}

Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
  this.maxFrameSize = size
}

Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
  this.maxHeaderListSize = size
}

// Only for testing
Parser.prototype.skipPreface = function skipPreface () {
  // Just some number bigger than 3.1, doesn't really matter for HTTP2
  this.setVersion(4)

  // Parse frame header!
  this.state = 'frame-head'
  this.waiting = constants.FRAME_HEADER_SIZE
}

Parser.prototype.execute = function execute (buffer, callback) {
  if (this.state === 'preface') { return this.onPreface(buffer, callback) }

  if (this.state === 'frame-head') {
    return this.onFrameHead(buffer, callback)
  }

  assert(this.state === 'frame-body' && this.pendingHeader !== null)

  var self = this
  var header = this.pendingHeader
  this.pendingHeader = null

  this.onFrameBody(header, buffer, function (err, frame) {
    if (err) {
      return callback(err)
    }

    self.state = 'frame-head'
    self.partial = false
    self.waiting = constants.FRAME_HEADER_SIZE
    callback(null, frame)
  })
}

Parser.prototype.executePartial = function executePartial (buffer, callback) {
  var header = this.pendingHeader

  assert.strictEqual(header.flags & constants.flags.PADDED, 0)

  if (this.window) { this.window.recv.update(-buffer.size) }

  callback(null, {
    type: 'DATA',
    id: header.id,

    // Partial DATA can't be FIN
    fin: false,
    data: buffer.take(buffer.size)
  })
}

Parser.prototype.onPreface = function onPreface (buffer, callback) {
  if (buffer.take(buffer.size).toString() !== constants.PREFACE) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid preface'))
  }

  this.skipPreface()
  callback(null, null)
}

Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
  var header = {
    length: buffer.readUInt24BE(),
    control: true,
    type: buffer.readUInt8(),
    flags: buffer.readUInt8(),
    id: buffer.readUInt32BE() & 0x7fffffff
  }

  if (header.length > this.maxFrameSize) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'Frame length OOB'))
  }

  header.control = header.type !== constants.frameType.DATA

  this.state = 'frame-body'
  this.pendingHeader = header
  this.waiting = header.length
  this.partial = !header.control

  // TODO(indutny): eventually support partial padded DATA
  if (this.partial) {
    this.partial = (header.flags & constants.flags.PADDED) === 0
  }

  callback(null, null)
}

Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
  var frameType = constants.frameType

  if (header.type === frameType.DATA) {
    this.onDataFrame(header, buffer, callback)
  } else if (header.type === frameType.HEADERS) {
    this.onHeadersFrame(header, buffer, callback)
  } else if (header.type === frameType.CONTINUATION) {
    this.onContinuationFrame(header, buffer, callback)
  } else if (header.type === frameType.WINDOW_UPDATE) {
    this.onWindowUpdateFrame(header, buffer, callback)
  } else if (header.type === frameType.RST_STREAM) {
    this.onRSTFrame(header, buffer, callback)
  } else if (header.type === frameType.SETTINGS) {
    this.onSettingsFrame(header, buffer, callback)
  } else if (header.type === frameType.PUSH_PROMISE) {
    this.onPushPromiseFrame(header, buffer, callback)
  } else if (header.type === frameType.PING) {
    this.onPingFrame(header, buffer, callback)
  } else if (header.type === frameType.GOAWAY) {
    this.onGoawayFrame(header, buffer, callback)
  } else if (header.type === frameType.PRIORITY) {
    this.onPriorityFrame(header, buffer, callback)
  } else if (header.type === frameType.X_FORWARDED_FOR) {
    this.onXForwardedFrame(header, buffer, callback)
  } else {
    this.onUnknownFrame(header, buffer, callback)
  }
}

Parser.prototype.onUnknownFrame = function onUnknownFrame (header, buffer, callback) {
  if (this._lastHeaderBlock !== null) {
    callback(this.error(constants.error.PROTOCOL_ERROR,
      'Received unknown frame in the middle of a header block'))
    return
  }
  callback(null, { type: 'unknown: ' + header.type })
}

Parser.prototype.unpadData = function unpadData (header, body, callback) {
  var isPadded = (header.flags & constants.flags.PADDED) !== 0

  if (!isPadded) { return callback(null, body) }

  if (!body.has(1)) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'Not enough space for padding'))
  }

  var pad = body.readUInt8()
  if (!body.has(pad)) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid padding size'))
  }

  var contents = body.clone(body.size - pad)
  body.skip(body.size)
  callback(null, contents)
}

Parser.prototype.onDataFrame = function onDataFrame (header, body, callback) {
  var isEndStream = (header.flags & constants.flags.END_STREAM) !== 0

  if (header.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Received DATA frame with stream=0'))
  }

  // Count received bytes
  if (this.window) {
    this.window.recv.update(-body.size)
  }

  this.unpadData(header, body, function (err, data) {
    if (err) {
      return callback(err)
    }

    callback(null, {
      type: 'DATA',
      id: header.id,
      fin: isEndStream,
      data: data.take(data.size)
    })
  })
}

Parser.prototype.initHeaderBlock = function initHeaderBlock (header,
  frame,
  block,
  callback) {
  if (this._lastHeaderBlock) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Duplicate Stream ID'))
  }

  this._lastHeaderBlock = {
    id: header.id,
    frame: frame,
    queue: [],
    size: 0
  }

  this.queueHeaderBlock(header, block, callback)
}

Parser.prototype.queueHeaderBlock = function queueHeaderBlock (header,
  block,
  callback) {
  var self = this
  var item = this._lastHeaderBlock
  if (!this._lastHeaderBlock || item.id !== header.id) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'No matching stream for continuation'))
  }

  var fin = (header.flags & constants.flags.END_HEADERS) !== 0

  var chunks = block.toChunks()
  for (var i = 0; i < chunks.length; i++) {
    var chunk = chunks[i]
    item.queue.push(chunk)
    item.size += chunk.length
  }

  if (item.size >= self.maxHeaderListSize) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Compressed header list is too large'))
  }

  if (!fin) { return callback(null, null) }
  this._lastHeaderBlock = null

  this.decompress.write(item.queue, function (err, chunks) {
    if (err) {
      return callback(self.error(constants.error.COMPRESSION_ERROR,
        err.message))
    }

    var headers = {}
    var size = 0
    for (var i = 0; i < chunks.length; i++) {
      var header = chunks[i]

      size += header.name.length + header.value.length + 32
      if (size >= self.maxHeaderListSize) {
        return callback(self.error(constants.error.PROTOCOL_ERROR,
          'Header list is too large'))
      }

      if (/[A-Z]/.test(header.name)) {
        return callback(self.error(constants.error.PROTOCOL_ERROR,
          'Header name must be lowercase'))
      }

      utils.addHeaderLine(header.name, header.value, headers)
    }

    item.frame.headers = headers
    item.frame.path = headers[':path']

    callback(null, item.frame)
  })
}

Parser.prototype.onHeadersFrame = function onHeadersFrame (header,
  body,
  callback) {
  var self = this

  if (header.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for HEADERS'))
  }

  this.unpadData(header, body, function (err, data) {
    if (err) { return callback(err) }

    var isPriority = (header.flags & constants.flags.PRIORITY) !== 0
    if (!data.has(isPriority ? 5 : 0)) {
      return callback(self.error(constants.error.FRAME_SIZE_ERROR,
        'Not enough data for HEADERS'))
    }

    var exclusive = false
    var dependency = 0
    var weight = constants.DEFAULT_WEIGHT
    if (isPriority) {
      dependency = data.readUInt32BE()
      exclusive = (dependency & 0x80000000) !== 0
      dependency &= 0x7fffffff

      // Weight's range is [1, 256]
      weight = data.readUInt8() + 1
    }

    if (dependency === header.id) {
      return callback(self.error(constants.error.PROTOCOL_ERROR,
        'Stream can\'t dependend on itself'))
    }

    var streamInfo = {
      type: 'HEADERS',
      id: header.id,
      priority: {
        parent: dependency,
        exclusive: exclusive,
        weight: weight
      },
      fin: (header.flags & constants.flags.END_STREAM) !== 0,
      writable: true,
      headers: null,
      path: null
    }

    self.initHeaderBlock(header, streamInfo, data, callback)
  })
}

Parser.prototype.onContinuationFrame = function onContinuationFrame (header,
  body,
  callback) {
  this.queueHeaderBlock(header, body, callback)
}

Parser.prototype.onRSTFrame = function onRSTFrame (header, body, callback) {
  if (body.size !== 4) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'RST_STREAM length not 4'))
  }

  if (header.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for RST_STREAM'))
  }

  callback(null, {
    type: 'RST',
    id: header.id,
    code: constants.errorByCode[body.readUInt32BE()]
  })
}

Parser.prototype._validateSettings = function _validateSettings (settings) {
  if (settings['enable_push'] !== undefined &&
      settings['enable_push'] !== 0 &&
      settings['enable_push'] !== 1) {
    return this.error(constants.error.PROTOCOL_ERROR,
      'SETTINGS_ENABLE_PUSH must be 0 or 1')
  }

  if (settings['initial_window_size'] !== undefined &&
      (settings['initial_window_size'] > constants.MAX_INITIAL_WINDOW_SIZE ||
       settings['initial_window_size'] < 0)) {
    return this.error(constants.error.FLOW_CONTROL_ERROR,
      'SETTINGS_INITIAL_WINDOW_SIZE is OOB')
  }

  if (settings['max_frame_size'] !== undefined &&
      (settings['max_frame_size'] > constants.ABSOLUTE_MAX_FRAME_SIZE ||
       settings['max_frame_size'] < constants.INITIAL_MAX_FRAME_SIZE)) {
    return this.error(constants.error.PROTOCOL_ERROR,
      'SETTINGS_MAX_FRAME_SIZE is OOB')
  }

  return undefined
}

Parser.prototype.onSettingsFrame = function onSettingsFrame (header,
  body,
  callback) {
  if (header.id !== 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for SETTINGS'))
  }

  var isAck = (header.flags & constants.flags.ACK) !== 0
  if (isAck && body.size !== 0) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'SETTINGS with ACK and non-zero length'))
  }

  if (isAck) {
    return callback(null, { type: 'ACK_SETTINGS' })
  }

  if (body.size % 6 !== 0) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'SETTINGS length not multiple of 6'))
  }

  var settings = {}
  while (!body.isEmpty()) {
    var id = body.readUInt16BE()
    var value = body.readUInt32BE()
    var name = constants.settingsIndex[id]

    if (name) {
      settings[name] = value
    }
  }

  var err = this._validateSettings(settings)
  if (err !== undefined) {
    return callback(err)
  }

  callback(null, {
    type: 'SETTINGS',
    settings: settings
  })
}

Parser.prototype.onPushPromiseFrame = function onPushPromiseFrame (header,
  body,
  callback) {
  if (header.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for PUSH_PROMISE'))
  }

  var self = this
  this.unpadData(header, body, function (err, data) {
    if (err) {
      return callback(err)
    }

    if (!data.has(4)) {
      return callback(self.error(constants.error.FRAME_SIZE_ERROR,
        'PUSH_PROMISE length less than 4'))
    }

    var streamInfo = {
      type: 'PUSH_PROMISE',
      id: header.id,
      fin: false,
      promisedId: data.readUInt32BE() & 0x7fffffff,
      headers: null,
      path: null
    }

    self.initHeaderBlock(header, streamInfo, data, callback)
  })
}

Parser.prototype.onPingFrame = function onPingFrame (header, body, callback) {
  if (body.size !== 8) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'PING length != 8'))
  }

  if (header.id !== 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for PING'))
  }

  var ack = (header.flags & constants.flags.ACK) !== 0
  callback(null, { type: 'PING', opaque: body.take(body.size), ack: ack })
}

Parser.prototype.onGoawayFrame = function onGoawayFrame (header,
  body,
  callback) {
  if (!body.has(8)) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'GOAWAY length < 8'))
  }

  if (header.id !== 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for GOAWAY'))
  }

  var frame = {
    type: 'GOAWAY',
    lastId: body.readUInt32BE(),
    code: constants.goawayByCode[body.readUInt32BE()]
  }

  if (body.size !== 0) { frame.debug = body.take(body.size) }

  callback(null, frame)
}

Parser.prototype.onPriorityFrame = function onPriorityFrame (header,
  body,
  callback) {
  if (body.size !== 5) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'PRIORITY length != 5'))
  }

  if (header.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for PRIORITY'))
  }

  var dependency = body.readUInt32BE()

  // Again the range is from 1 to 256
  var weight = body.readUInt8() + 1

  if (dependency === header.id) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Stream can\'t dependend on itself'))
  }

  callback(null, {
    type: 'PRIORITY',
    id: header.id,
    priority: {
      exclusive: (dependency & 0x80000000) !== 0,
      parent: dependency & 0x7fffffff,
      weight: weight
    }
  })
}

Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (header,
  body,
  callback) {
  if (body.size !== 4) {
    return callback(this.error(constants.error.FRAME_SIZE_ERROR,
      'WINDOW_UPDATE length != 4'))
  }

  var delta = body.readInt32BE()
  if (delta === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'WINDOW_UPDATE delta == 0'))
  }

  callback(null, {
    type: 'WINDOW_UPDATE',
    id: header.id,
    delta: delta
  })
}

Parser.prototype.onXForwardedFrame = function onXForwardedFrame (header,
  body,
  callback) {
  callback(null, {
    type: 'X_FORWARDED_FOR',
    host: body.take(body.size).toString()
  })
}


code:
'use strict'

var transport = require('../../../spdy-transport')
var base = transport.protocol.base

exports.FRAME_HEADER_SIZE = 8

exports.PING_OPAQUE_SIZE = 4

exports.MAX_CONCURRENT_STREAMS = Infinity
exports.DEFAULT_MAX_HEADER_LIST_SIZE = Infinity

exports.DEFAULT_WEIGHT = 16

exports.frameType = {
  SYN_STREAM: 1,
  SYN_REPLY: 2,
  RST_STREAM: 3,
  SETTINGS: 4,
  PING: 6,
  GOAWAY: 7,
  HEADERS: 8,
  WINDOW_UPDATE: 9,

  // Custom
  X_FORWARDED_FOR: 0xf000
}

exports.flags = {
  FLAG_FIN: 0x01,
  FLAG_COMPRESSED: 0x02,
  FLAG_UNIDIRECTIONAL: 0x02
}

exports.error = {
  PROTOCOL_ERROR: 1,
  INVALID_STREAM: 2,
  REFUSED_STREAM: 3,
  UNSUPPORTED_VERSION: 4,
  CANCEL: 5,
  INTERNAL_ERROR: 6,
  FLOW_CONTROL_ERROR: 7,
  STREAM_IN_USE: 8,
  // STREAM_ALREADY_CLOSED: 9
  STREAM_CLOSED: 9,
  INVALID_CREDENTIALS: 10,
  FRAME_TOO_LARGE: 11
}
exports.errorByCode = base.utils.reverse(exports.error)

exports.settings = {
  FLAG_SETTINGS_PERSIST_VALUE: 1,
  FLAG_SETTINGS_PERSISTED: 2,

  SETTINGS_UPLOAD_BANDWIDTH: 1,
  SETTINGS_DOWNLOAD_BANDWIDTH: 2,
  SETTINGS_ROUND_TRIP_TIME: 3,
  SETTINGS_MAX_CONCURRENT_STREAMS: 4,
  SETTINGS_CURRENT_CWND: 5,
  SETTINGS_DOWNLOAD_RETRANS_RATE: 6,
  SETTINGS_INITIAL_WINDOW_SIZE: 7,
  SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: 8
}

exports.settingsIndex = [
  null,

  'upload_bandwidth',
  'download_bandwidth',
  'round_trip_time',
  'max_concurrent_streams',
  'current_cwnd',
  'download_retrans_rate',
  'initial_window_size',
  'client_certificate_vector_size'
]

exports.DEFAULT_WINDOW = 64 * 1024
exports.MAX_INITIAL_WINDOW_SIZE = 2147483647

exports.goaway = {
  OK: 0,
  PROTOCOL_ERROR: 1,
  INTERNAL_ERROR: 2
}
exports.goawayByCode = base.utils.reverse(exports.goaway)

exports.statusReason = {
  100: 'Continue',
  101: 'Switching Protocols',
  102: 'Processing', // RFC 2518, obsoleted by RFC 4918
  200: 'OK',
  201: 'Created',
  202: 'Accepted',
  203: 'Non-Authoritative Information',
  204: 'No Content',
  205: 'Reset Content',
  206: 'Partial Content',
  207: 'Multi-Status', // RFC 4918
  300: 'Multiple Choices',
  301: 'Moved Permanently',
  302: 'Moved Temporarily',
  303: 'See Other',
  304: 'Not Modified',
  305: 'Use Proxy',
  307: 'Temporary Redirect',
  308: 'Permanent Redirect', // RFC 7238
  400: 'Bad Request',
  401: 'Unauthorized',
  402: 'Payment Required',
  403: 'Forbidden',
  404: 'Not Found',
  405: 'Method Not Allowed',
  406: 'Not Acceptable',
  407: 'Proxy Authentication Required',
  408: 'Request Time-out',
  409: 'Conflict',
  410: 'Gone',
  411: 'Length Required',
  412: 'Precondition Failed',
  413: 'Request Entity Too Large',
  414: 'Request-URI Too Large',
  415: 'Unsupported Media Type',
  416: 'Requested Range Not Satisfiable',
  417: 'Expectation Failed',
  418: 'I\'m a teapot', // RFC 2324
  422: 'Unprocessable Entity', // RFC 4918
  423: 'Locked', // RFC 4918
  424: 'Failed Dependency', // RFC 4918
  425: 'Unordered Collection', // RFC 4918
  426: 'Upgrade Required', // RFC 2817
  428: 'Precondition Required', // RFC 6585
  429: 'Too Many Requests', // RFC 6585
  431: 'Request Header Fields Too Large', // RFC 6585
  500: 'Internal Server Error',
  501: 'Not Implemented',
  502: 'Bad Gateway',
  503: 'Service Unavailable',
  504: 'Gateway Time-out',
  505: 'HTTP Version Not Supported',
  506: 'Variant Also Negotiates', // RFC 2295
  507: 'Insufficient Storage', // RFC 4918
  509: 'Bandwidth Limit Exceeded',
  510: 'Not Extended', // RFC 2774
  511: 'Network Authentication Required' // RFC 6585
}

license: mit


code:
'use strict'

var dictionary = {}
module.exports = dictionary

dictionary[2] = Buffer.from([
  'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
  'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
  'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
  '-agent10010120020120220320420520630030130230330430530630740040140240340440',
  '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
  'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
  'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
  'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
  'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
  'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
  'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
  'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
  '.1statusversionurl\x00'
].join(''))

dictionary[3] = Buffer.from([
  0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti
  0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h
  0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p
  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p
  0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de
  0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete....
  0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace...
  0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept.
  0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
  0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse
  0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc
  0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco
  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding....
  0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l
  0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage.
  0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
  0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges
  0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age.
  0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow
  0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth
  0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio
  0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac
  0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr
  0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co
  0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection
  0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont
  0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base
  0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont
  0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco
  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding....
  0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content-
  0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language
  0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont
  0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng
  0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co
  0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo
  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation..
  0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
  0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5...
  0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content
  0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range..
  0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
  0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type..
  0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date..
  0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag..
  0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect
  0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi
  0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f
  0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h
  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i
  0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match.
  0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo
  0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s
  0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince....
  0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none-
  0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match...
  0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang
  0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if-
  0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi
  0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since
  0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last
  0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie
  0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc
  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation...
  0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for
  0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards...
  0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma.
  0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy
  0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent
  0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate...
  0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a
  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza
  0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion....
  0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range...
  0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer
  0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr
  0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after.
  0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve
  0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te.
  0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail
  0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr
  0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e
  0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding.
  0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra
  0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us
  0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent
  0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary
  0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via.
  0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni
  0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww
  0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen
  0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate..
  0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method
  0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get.
  0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu
  0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200
  0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v
  0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion..
  0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1
  0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur
  0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub
  0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s
  0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki
  0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee
  0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive.
  0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi
  0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012
  0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205
  0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030
  0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043
  0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307
  0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540
  0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084
  0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411
  0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341
  0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164
  0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504
  0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N
  0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho
  0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative
  0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa
  0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204.
  0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte
  0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo
  0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm
  0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4
  0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R
  0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40
  0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth
  0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40
  0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid
  0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N
  0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found
  0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte
  0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser
  0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro
  0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not
  0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme
  0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503.
  0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service.
  0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila
  0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F
  0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A
  0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J
  0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A
  0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept.
  0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov.
  0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0
  0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon
  0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W
  0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu.
  0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa
  0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun..
  0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk
  0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text.
  0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima
  0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i
  0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg
  0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g
  0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli
  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
  0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli
  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
  0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml
  0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl
  0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text
  0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr
  0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ
  0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat
  0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age
  0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de
  0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd
  0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse
  0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c
  0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i
  0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859-
  0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-..
  0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0.
])

dictionary[3.1] = dictionary[3]


code:
'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)
}


code:
'use strict'

exports.name = 'spdy'

exports.dictionary = require('./dictionary')
exports.constants = require('./constants')
exports.parser = require('./parser')
exports.framer = require('./framer')
exports.compressionPool = require('./zlib-pool')


code:
'use strict'

var parser = exports

var transport = require('../../../spdy-transport')
var base = transport.protocol.base
var utils = base.utils
var constants = require('./constants')

var assert = require('assert')
var util = require('util')
var OffsetBuffer = require('obuf')

function Parser (options) {
  base.Parser.call(this, options)

  this.isServer = options.isServer
  this.waiting = constants.FRAME_HEADER_SIZE
  this.state = 'frame-head'
  this.pendingHeader = null
}
util.inherits(Parser, base.Parser)

parser.create = function create (options) {
  return new Parser(options)
}

Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
  // http2-only
}

Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
  // http2-only
}

// Only for testing
Parser.prototype.skipPreface = function skipPreface () {
}

Parser.prototype.execute = function execute (buffer, callback) {
  if (this.state === 'frame-head') { return this.onFrameHead(buffer, callback) }

  assert(this.state === 'frame-body' && this.pendingHeader !== null)

  var self = this
  var header = this.pendingHeader
  this.pendingHeader = null

  this.onFrameBody(header, buffer, function (err, frame) {
    if (err) {
      return callback(err)
    }

    self.state = 'frame-head'
    self.waiting = constants.FRAME_HEADER_SIZE
    self.partial = false
    callback(null, frame)
  })
}

Parser.prototype.executePartial = function executePartial (buffer, callback) {
  var header = this.pendingHeader

  if (this.window) {
    this.window.recv.update(-buffer.size)
  }

  // DATA frame
  callback(null, {
    type: 'DATA',
    id: header.id,

    // Partial DATA can't be FIN
    fin: false,
    data: buffer.take(buffer.size)
  })
}

Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
  var header = {
    control: (buffer.peekUInt8() & 0x80) === 0x80,
    version: null,
    type: null,
    id: null,
    flags: null,
    length: null
  }

  if (header.control) {
    header.version = buffer.readUInt16BE() & 0x7fff
    header.type = buffer.readUInt16BE()
  } else {
    header.id = buffer.readUInt32BE(0) & 0x7fffffff
  }
  header.flags = buffer.readUInt8()
  header.length = buffer.readUInt24BE()

  if (this.version === null && header.control) {
    // TODO(indutny): do ProtocolError here and in the rest of errors
    if (header.version !== 2 && header.version !== 3) {
      return callback(new Error('Unsupported SPDY version: ' + header.version))
    }
    this.setVersion(header.version)
  }

  this.state = 'frame-body'
  this.waiting = header.length
  this.pendingHeader = header
  this.partial = !header.control

  callback(null, null)
}

Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
  // Data frame
  if (!header.control) {
    // Count received bytes
    if (this.window) {
      this.window.recv.update(-buffer.size)
    }

    // No support for compressed DATA
    if ((header.flags & constants.flags.FLAG_COMPRESSED) !== 0) {
      return callback(new Error('DATA compression not supported'))
    }

    if (header.id === 0) {
      return callback(this.error(constants.error.PROTOCOL_ERROR,
        'Invalid stream id for DATA'))
    }

    return callback(null, {
      type: 'DATA',
      id: header.id,
      fin: (header.flags & constants.flags.FLAG_FIN) !== 0,
      data: buffer.take(buffer.size)
    })
  }

  if (header.type === 0x01 || header.type === 0x02) { // SYN_STREAM or SYN_REPLY
    this.onSynHeadFrame(header.type, header.flags, buffer, callback)
  } else if (header.type === 0x03) { // RST_STREAM
    this.onRSTFrame(buffer, callback)
  } else if (header.type === 0x04) { // SETTINGS
    this.onSettingsFrame(buffer, callback)
  } else if (header.type === 0x05) {
    callback(null, { type: 'NOOP' })
  } else if (header.type === 0x06) { // PING
    this.onPingFrame(buffer, callback)
  } else if (header.type === 0x07) { // GOAWAY
    this.onGoawayFrame(buffer, callback)
  } else if (header.type === 0x08) { // HEADERS
    this.onHeaderFrames(buffer, callback)
  } else if (header.type === 0x09) { // WINDOW_UPDATE
    this.onWindowUpdateFrame(buffer, callback)
  } else if (header.type === 0xf000) { // X-FORWARDED
    this.onXForwardedFrame(buffer, callback)
  } else {
    callback(null, { type: 'unknown: ' + header.type })
  }
}

Parser.prototype._filterHeader = function _filterHeader (headers, name) {
  var res = {}
  var keys = Object.keys(headers)

  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    if (key !== name) {
      res[key] = headers[key]
    }
  }

  return res
}

Parser.prototype.onSynHeadFrame = function onSynHeadFrame (type,
  flags,
  body,
  callback) {
  var self = this
  var stream = type === 0x01
  var offset = stream ? 10 : this.version === 2 ? 6 : 4

  if (!body.has(offset)) {
    return callback(new Error('SynHead OOB'))
  }

  var head = body.clone(offset)
  body.skip(offset)
  this.parseKVs(body, function (err, headers) {
    if (err) {
      return callback(err)
    }

    if (stream &&
        (!headers[':method'] || !headers[':path'])) {
      return callback(new Error('Missing `:method` and/or `:path` header'))
    }

    var id = head.readUInt32BE() & 0x7fffffff

    if (id === 0) {
      return callback(self.error(constants.error.PROTOCOL_ERROR,
        'Invalid stream id for HEADERS'))
    }

    var associated = stream ? head.readUInt32BE() & 0x7fffffff : 0
    var priority = stream
      ? head.readUInt8() >> 5
      : utils.weightToPriority(constants.DEFAULT_WEIGHT)
    var fin = (flags & constants.flags.FLAG_FIN) !== 0
    var unidir = (flags & constants.flags.FLAG_UNIDIRECTIONAL) !== 0
    var path = headers[':path']

    var isPush = stream && associated !== 0

    var weight = utils.priorityToWeight(priority)
    var priorityInfo = {
      weight: weight,
      exclusive: false,
      parent: 0
    }

    if (!isPush) {
      callback(null, {
        type: 'HEADERS',
        id: id,
        priority: priorityInfo,
        fin: fin,
        writable: !unidir,
        headers: headers,
        path: path
      })
      return
    }

    if (stream && !headers[':status']) {
      return callback(new Error('Missing `:status` header'))
    }

    var filteredHeaders = self._filterHeader(headers, ':status')

    callback(null, [ {
      type: 'PUSH_PROMISE',
      id: associated,
      fin: false,
      promisedId: id,
      headers: filteredHeaders,
      path: path
    }, {
      type: 'HEADERS',
      id: id,
      fin: fin,
      priority: priorityInfo,
      writable: true,
      path: undefined,
      headers: {
        ':status': headers[':status']
      }
    }])
  })
}

Parser.prototype.onHeaderFrames = function onHeaderFrames (body, callback) {
  var offset = this.version === 2 ? 6 : 4
  if (!body.has(offset)) {
    return callback(new Error('HEADERS OOB'))
  }

  var streamId = body.readUInt32BE() & 0x7fffffff
  if (this.version === 2) { body.skip(2) }

  this.parseKVs(body, function (err, headers) {
    if (err) { return callback(err) }

    callback(null, {
      type: 'HEADERS',
      priority: {
        parent: 0,
        exclusive: false,
        weight: constants.DEFAULT_WEIGHT
      },
      id: streamId,
      fin: false,
      writable: true,
      path: undefined,
      headers: headers
    })
  })
}

Parser.prototype.parseKVs = function parseKVs (buffer, callback) {
  var self = this

  this.decompress.write(buffer.toChunks(), function (err, chunks) {
    if (err) {
      return callback(err)
    }

    var buffer = new OffsetBuffer()
    for (var i = 0; i < chunks.length; i++) {
      buffer.push(chunks[i])
    }

    var size = self.version === 2 ? 2 : 4
    if (!buffer.has(size)) { return callback(new Error('KV OOB')) }

    var count = self.version === 2
      ? buffer.readUInt16BE()
      : buffer.readUInt32BE()

    var headers = {}

    function readString () {
      if (!buffer.has(size)) { return null }
      var len = self.version === 2
        ? buffer.readUInt16BE()
        : buffer.readUInt32BE()

      if (!buffer.has(len)) { return null }

      var value = buffer.take(len)
      return value.toString()
    }

    while (count > 0) {
      var key = readString()
      var value = readString()

      if (key === null || value === null) {
        return callback(new Error('Headers OOB'))
      }

      if (self.version < 3) {
        var isInternal = /^(method|version|url|host|scheme|status)$/.test(key)
        if (key === 'url') {
          key = 'path'
        }
        if (isInternal) {
          key = ':' + key
        }
      }

      // Compatibility with HTTP2
      if (key === ':status') {
        value = value.split(/ /g, 2)[0]
      }

      count--
      if (key === ':host') {
        key = ':authority'
      }

      // Skip version, not present in HTTP2
      if (key === ':version') {
        continue
      }

      value = value.split(/\0/g)
      for (var j = 0; j < value.length; j++) {
        utils.addHeaderLine(key, value[j], headers)
      }
    }

    callback(null, headers)
  })
}

Parser.prototype.onRSTFrame = function onRSTFrame (body, callback) {
  if (!body.has(8)) { return callback(new Error('RST OOB')) }

  var frame = {
    type: 'RST',
    id: body.readUInt32BE() & 0x7fffffff,
    code: constants.errorByCode[body.readUInt32BE()]
  }

  if (frame.id === 0) {
    return callback(this.error(constants.error.PROTOCOL_ERROR,
      'Invalid stream id for RST'))
  }

  if (body.size !== 0) {
    frame.extra = body.take(body.size)
  }
  callback(null, frame)
}

Parser.prototype.onSettingsFrame = function onSettingsFrame (body, callback) {
  if (!body.has(4)) {
    return callback(new Error('SETTINGS OOB'))
  }

  var settings = {}
  var number = body.readUInt32BE()
  var idMap = {
    1: 'upload_bandwidth',
    2: 'download_bandwidth',
    3: 'round_trip_time',
    4: 'max_concurrent_streams',
    5: 'current_cwnd',
    6: 'download_retrans_rate',
    7: 'initial_window_size',
    8: 'client_certificate_vector_size'
  }

  if (!body.has(number * 8)) {
    return callback(new Error('SETTINGS OOB#2'))
  }

  for (var i = 0; i < number; i++) {
    var id = this.version === 2
      ? body.readUInt32LE()
      : body.readUInt32BE()

    var flags = (id >> 24) & 0xff
    id = id & 0xffffff

    // Skip persisted settings
    if (flags & 0x2) { continue }

    var name = idMap[id]

    settings[name] = body.readUInt32BE()
  }

  callback(null, {
    type: 'SETTINGS',
    settings: settings
  })
}

Parser.prototype.onPingFrame = function onPingFrame (body, callback) {
  if (!body.has(4)) {
    return callback(new Error('PING OOB'))
  }

  var isServer = this.isServer
  var opaque = body.clone(body.size).take(body.size)
  var id = body.readUInt32BE()
  var ack = isServer ? (id % 2 === 0) : (id % 2 === 1)

  callback(null, { type: 'PING', opaque: opaque, ack: ack })
}

Parser.prototype.onGoawayFrame = function onGoawayFrame (body, callback) {
  if (!body.has(8)) {
    return callback(new Error('GOAWAY OOB'))
  }

  callback(null, {
    type: 'GOAWAY',
    lastId: body.readUInt32BE() & 0x7fffffff,
    code: constants.goawayByCode[body.readUInt32BE()]
  })
}

Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (body,
  callback) {
  if (!body.has(8)) {
    return callback(new Error('WINDOW_UPDATE OOB'))
  }

  callback(null, {
    type: 'WINDOW_UPDATE',
    id: body.readUInt32BE() & 0x7fffffff,
    delta: body.readInt32BE()
  })
}

Parser.prototype.onXForwardedFrame = function onXForwardedFrame (body,
  callback) {
  if (!body.has(4)) {
    return callback(new Error('X_FORWARDED OOB'))
  }

  var len = body.readUInt32BE()
  if (!body.has(len)) { return callback(new Error('X_FORWARDED host length OOB')) }

  callback(null, {
    type: 'X_FORWARDED_FOR',
    host: body.take(len).toString()
  })
}


code:
'use strict'

var zlibpool = exports
var zlib = require('zlib')

var transport = require('../../../spdy-transport')

// TODO(indutny): think about it, why has it always been Z_SYNC_FLUSH here.
// It should be possible to manually flush stuff after the write instead
function createDeflate (version, compression) {
  var deflate = zlib.createDeflate({
    dictionary: transport.protocol.spdy.dictionary[version],
    flush: zlib.Z_SYNC_FLUSH,
    windowBits: 11,
    level: compression ? zlib.Z_DEFAULT_COMPRESSION : zlib.Z_NO_COMPRESSION
  })

  // For node.js v0.8
  deflate._flush = zlib.Z_SYNC_FLUSH

  return deflate
}

function createInflate (version) {
  var inflate = zlib.createInflate({
    dictionary: transport.protocol.spdy.dictionary[version],
    flush: zlib.Z_SYNC_FLUSH
  })

  // For node.js v0.8
  inflate._flush = zlib.Z_SYNC_FLUSH

  return inflate
}

function Pool (compression) {
  this.compression = compression
  this.pool = {
    2: [],
    3: [],
    3.1: []
  }
}

zlibpool.create = function create (compression) {
  return new Pool(compression)
}

Pool.prototype.get = function get (version) {
  if (this.pool[version].length > 0) {
    return this.pool[version].pop()
  } else {
    var id = version

    return {
      version: version,
      compress: createDeflate(id, this.compression),
      decompress: createInflate(id)
    }
  }
}

Pool.prototype.put = function put (pair) {
  this.pool[pair.version].push(pair)
}


code:
'use strict'

var transport = require('../spdy-transport')

var assert = require('assert')
var util = require('util')

var debug = {
  client: require('debug')('spdy:stream:client'),
  server: require('debug')('spdy:stream:server')
}
var Duplex = require('readable-stream').Duplex

function Stream (connection, options) {
  Duplex.call(this)

  var connectionState = connection._spdyState

  var state = {}
  this._spdyState = state

  this.id = options.id
  this.method = options.method
  this.path = options.path
  this.host = options.host
  this.headers = options.headers || {}
  this.connection = connection
  this.parent = options.parent || null

  state.socket = null
  state.protocol = connectionState.protocol
  state.constants = state.protocol.constants

  // See _initPriority()
  state.priority = null

  state.version = this.connection.getVersion()
  state.isServer = this.connection.isServer()
  state.debug = state.isServer ? debug.server : debug.client

  state.framer = connectionState.framer
  state.parser = connectionState.parser

  state.request = options.request
  state.needResponse = options.request
  state.window = connectionState.streamWindow.clone(options.id)
  state.sessionWindow = connectionState.window
  state.maxChunk = connectionState.maxChunk

  // Can't send incoming request
  // (See `.send()` method)
  state.sent = !state.request

  state.readable = options.readable !== false
  state.writable = options.writable !== false

  state.aborted = false

  state.corked = 0
  state.corkQueue = []

  state.timeout = new transport.utils.Timeout(this)

  this.on('finish', this._onFinish)
  this.on('end', this._onEnd)

  var self = this
  function _onWindowOverflow () {
    self._onWindowOverflow()
  }

  state.window.recv.on('overflow', _onWindowOverflow)
  state.window.send.on('overflow', _onWindowOverflow)

  this._initPriority(options.priority)

  if (!state.readable) { this.push(null) }
  if (!state.writable) {
    this._writableState.ended = true
    this._writableState.finished = true
  }
}
util.inherits(Stream, Duplex)
exports.Stream = Stream

Stream.prototype._init = function _init (socket) {
  this.socket = socket
}

Stream.prototype._initPriority = function _initPriority (priority) {
  var state = this._spdyState
  var connectionState = this.connection._spdyState
  var root = connectionState.priorityRoot

  if (!priority) {
    state.priority = root.addDefault(this.id)
    return
  }

  state.priority = root.add({
    id: this.id,
    parent: priority.parent,
    weight: priority.weight,
    exclusive: priority.exclusive
  })
}

Stream.prototype._handleFrame = function _handleFrame (frame) {
  var state = this._spdyState

  // Ignore any kind of data after abort
  if (state.aborted) {
    state.debug('id=%d ignoring frame=%s after abort', this.id, frame.type)
    return
  }

  // Restart the timer on incoming frames
  state.timeout.reset()

  if (frame.type === 'DATA') {
    this._handleData(frame)
  } else if (frame.type === 'HEADERS') {
    this._handleHeaders(frame)
  } else if (frame.type === 'RST') {
    this._handleRST(frame)
  } else if (frame.type === 'WINDOW_UPDATE') { this._handleWindowUpdate(frame) } else if (frame.type === 'PRIORITY') {
    this._handlePriority(frame)
  } else if (frame.type === 'PUSH_PROMISE') { this._handlePushPromise(frame) }

  if (frame.fin) {
    state.debug('id=%d end', this.id)
    this.push(null)
  }
}

function checkAborted (stream, state, callback) {
  if (state.aborted) {
    state.debug('id=%d abort write', stream.id)
    process.nextTick(function () {
      callback(new Error('Stream write aborted'))
    })
    return true
  }

  return false
}

function _send (stream, state, data, callback) {
  if (checkAborted(stream, state, callback)) {
    return
  }

  state.debug('id=%d presend=%d', stream.id, data.length)

  state.timeout.reset()

  state.window.send.update(-data.length, function () {
    if (checkAborted(stream, state, callback)) {
      return
    }

    state.debug('id=%d send=%d', stream.id, data.length)

    state.timeout.reset()

    state.framer.dataFrame({
      id: stream.id,
      priority: state.priority.getPriority(),
      fin: false,
      data: data
    }, function (err) {
      state.debug('id=%d postsend=%d', stream.id, data.length)
      callback(err)
    })
  })
}

Stream.prototype._write = function _write (data, enc, callback) {
  var state = this._spdyState

  // Send the request if it wasn't sent
  if (!state.sent) { this.send() }

  // Writes should come after pending control frames (response and headers)
  if (state.corked !== 0) {
    var self = this
    state.corkQueue.push(function () {
      self._write(data, enc, callback)
    })
    return
  }

  // Split DATA in chunks to prevent window from going negative
  this._splitStart(data, _send, callback)
}

Stream.prototype._splitStart = function _splitStart (data, onChunk, callback) {
  return this._split(data, 0, onChunk, callback)
}

Stream.prototype._split = function _split (data, offset, onChunk, callback) {
  if (offset === data.length) {
    return process.nextTick(callback)
  }

  var state = this._spdyState
  var local = state.window.send
  var session = state.sessionWindow.send

  var availSession = Math.max(0, session.getCurrent())
  if (availSession === 0) {
    availSession = session.getMax()
  }
  var availLocal = Math.max(0, local.getCurrent())
  if (availLocal === 0) {
    availLocal = local.getMax()
  }

  var avail = Math.min(availSession, availLocal)
  avail = Math.min(avail, state.maxChunk)

  var self = this

  if (avail === 0) {
    state.window.send.update(0, function () {
      self._split(data, offset, onChunk, callback)
    })
    return
  }

  // Split data in chunks in a following way:
  var limit = avail
  var size = Math.min(data.length - offset, limit)

  var chunk = data.slice(offset, offset + size)

  onChunk(this, state, chunk, function (err) {
    if (err) { return callback(err) }

    // Get the next chunk
    self._split(data, offset + size, onChunk, callback)
  })
}

Stream.prototype._read = function _read () {
  var state = this._spdyState

  if (!state.window.recv.isDraining()) {
    return
  }

  var delta = state.window.recv.getDelta()

  state.debug('id=%d window emptying, update by %d', this.id, delta)

  state.window.recv.update(delta)
  state.framer.windowUpdateFrame({
    id: this.id,
    delta: delta
  })
}

Stream.prototype._handleData = function _handleData (frame) {
  var state = this._spdyState

  // DATA on ended or not readable stream!
  if (!state.readable || this._readableState.ended) {
    state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
    return
  }

  state.debug('id=%d recv=%d', this.id, frame.data.length)
  state.window.recv.update(-frame.data.length)

  this.push(frame.data)
}

Stream.prototype._handleRST = function _handleRST (frame) {
  if (frame.code !== 'CANCEL') {
    this.emit('error', new Error('Got RST: ' + frame.code))
  }
  this.abort()
}

Stream.prototype._handleWindowUpdate = function _handleWindowUpdate (frame) {
  var state = this._spdyState

  state.window.send.update(frame.delta)
}

Stream.prototype._onWindowOverflow = function _onWindowOverflow () {
  var state = this._spdyState

  state.debug('id=%d window overflow', this.id)
  state.framer.rstFrame({ id: this.id, code: 'FLOW_CONTROL_ERROR' })

  this.aborted = true
  this.emit('error', new Error('HTTP2 window overflow'))
}

Stream.prototype._handlePriority = function _handlePriority (frame) {
  var state = this._spdyState

  state.priority.remove()
  state.priority = null
  this._initPriority(frame.priority)

  // Mostly for testing purposes
  this.emit('priority', frame.priority)
}

Stream.prototype._handleHeaders = function _handleHeaders (frame) {
  var state = this._spdyState

  if (!state.readable || this._readableState.ended) {
    state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
    return
  }

  if (state.needResponse) {
    return this._handleResponse(frame)
  }

  this.emit('headers', frame.headers)
}

Stream.prototype._handleResponse = function _handleResponse (frame) {
  var state = this._spdyState

  if (frame.headers[':status'] === undefined) {
    state.framer.rstFrame({ id: this.id, code: 'PROTOCOL_ERROR' })
    return
  }

  state.needResponse = false
  this.emit('response', frame.headers[':status'] | 0, frame.headers)
}

Stream.prototype._onFinish = function _onFinish () {
  var state = this._spdyState

  // Send the request if it wasn't sent
  if (!state.sent) {
    // NOTE: will send HEADERS with FIN flag
    this.send()
  } else {
    // Just an `.end()` without any writes will trigger immediate `finish` event
    // without any calls to `_write()`.
    if (state.corked !== 0) {
      var self = this
      state.corkQueue.push(function () {
        self._onFinish()
      })
      return
    }

    state.framer.dataFrame({
      id: this.id,
      priority: state.priority.getPriority(),
      fin: true,
      data: Buffer.alloc(0)
    })
  }

  this._maybeClose()
}

Stream.prototype._onEnd = function _onEnd () {
  this._maybeClose()
}

Stream.prototype._checkEnded = function _checkEnded (callback) {
  var state = this._spdyState

  var ended = false
  if (state.aborted) { ended = true }

  if (!state.writable || this._writableState.finished) { ended = true }

  if (!ended) {
    return true
  }

  if (!callback) {
    return false
  }

  var err = new Error('Ended stream can\'t send frames')
  process.nextTick(function () {
    callback(err)
  })

  return false
}

Stream.prototype._maybeClose = function _maybeClose () {
  var state = this._spdyState

  // .abort() emits `close`
  if (state.aborted) {
    return
  }

  if ((!state.readable || this._readableState.ended) &&
      this._writableState.finished) {
    // Clear timeout
    state.timeout.set(0)

    this.emit('close')
  }
}

Stream.prototype._handlePushPromise = function _handlePushPromise (frame) {
  var push = this.connection._createStream({
    id: frame.promisedId,
    parent: this,
    push: true,
    request: true,
    method: frame.headers[':method'],
    path: frame.headers[':path'],
    host: frame.headers[':authority'],
    priority: frame.priority,
    headers: frame.headers,
    writable: false
  })

  // GOAWAY
  if (this.connection._isGoaway(push.id)) {
    return
  }

  if (!this.emit('pushPromise', push)) {
    push.abort()
  }
}

Stream.prototype._hardCork = function _hardCork () {
  var state = this._spdyState

  this.cork()
  state.corked++
}

Stream.prototype._hardUncork = function _hardUncork () {
  var state = this._spdyState

  this.uncork()
  state.corked--
  if (state.corked !== 0) {
    return
  }

  // Invoke callbacks
  var queue = state.corkQueue
  state.corkQueue = []
  for (var i = 0; i < queue.length; i++) {
    queue[i]()
  }
}

Stream.prototype._sendPush = function _sendPush (status, response, callback) {
  var self = this
  var state = this._spdyState

  this._hardCork()
  state.framer.pushFrame({
    id: this.parent.id,
    promisedId: this.id,
    priority: state.priority.toJSON(),
    path: this.path,
    host: this.host,
    method: this.method,
    status: status,
    headers: this.headers,
    response: response
  }, function (err) {
    self._hardUncork()

    callback(err)
  })
}

Stream.prototype._wasSent = function _wasSent () {
  var state = this._spdyState
  return state.sent
}

// Public API

Stream.prototype.send = function send (callback) {
  var state = this._spdyState

  if (state.sent) {
    var err = new Error('Stream was already sent')
    process.nextTick(function () {
      if (callback) {
        callback(err)
      }
    })
    return
  }

  state.sent = true
  state.timeout.reset()

  // GET requests should always be auto-finished
  if (this.method === 'GET') {
    this._writableState.ended = true
    this._writableState.finished = true
  }

  // TODO(indunty): ideally it should just take a stream object as an input
  var self = this
  this._hardCork()
  state.framer.requestFrame({
    id: this.id,
    method: this.method,
    path: this.path,
    host: this.host,
    priority: state.priority.toJSON(),
    headers: this.headers,
    fin: this._writableState.finished
  }, function (err) {
    self._hardUncork()

    if (!callback) {
      return
    }

    callback(err)
  })
}

Stream.prototype.respond = function respond (status, headers, callback) {
  var self = this
  var state = this._spdyState
  assert(!state.request, 'Can\'t respond on request')

  state.timeout.reset()

  if (!this._checkEnded(callback)) { return }

  var frame = {
    id: this.id,
    status: status,
    headers: headers
  }
  this._hardCork()
  state.framer.responseFrame(frame, function (err) {
    self._hardUncork()
    if (callback) { callback(err) }
  })
}

Stream.prototype.setWindow = function setWindow (size) {
  var state = this._spdyState

  state.timeout.reset()

  if (!this._checkEnded()) {
    return
  }

  state.debug('id=%d force window max=%d', this.id, size)
  state.window.recv.setMax(size)

  var delta = state.window.recv.getDelta()
  if (delta === 0) { return }

  state.framer.windowUpdateFrame({
    id: this.id,
    delta: delta
  })
  state.window.recv.update(delta)
}

Stream.prototype.sendHeaders = function sendHeaders (headers, callback) {
  var self = this
  var state = this._spdyState

  state.timeout.reset()

  if (!this._checkEnded(callback)) {
    return
  }

  // Request wasn't yet send, coalesce headers
  if (!state.sent) {
    this.headers = Object.assign({}, this.headers)
    Object.assign(this.headers, headers)
    process.nextTick(function () {
      if (callback) {
        callback(null)
      }
    })
    return
  }

  this._hardCork()
  state.framer.headersFrame({
    id: this.id,
    headers: headers
  }, function (err) {
    self._hardUncork()
    if (callback) { callback(err) }
  })
}

Stream.prototype._destroy = function destroy () {
  this.abort()
}

Stream.prototype.abort = function abort (code, callback) {
  var state = this._spdyState

  // .abort(callback)
  if (typeof code === 'function') {
    callback = code
    code = null
  }

  if (this._readableState.ended && this._writableState.finished) {
    state.debug('id=%d already closed', this.id)
    if (callback) {
      process.nextTick(callback)
    }
    return
  }

  if (state.aborted) {
    state.debug('id=%d already aborted', this.id)
    if (callback) { process.nextTick(callback) }
    return
  }

  state.aborted = true
  state.debug('id=%d abort', this.id)

  this.setTimeout(0)

  var abortCode = code || 'CANCEL'

  state.framer.rstFrame({
    id: this.id,
    code: abortCode
  })

  var self = this
  process.nextTick(function () {
    if (callback) {
      callback(null)
    }
    self.emit('close', new Error('Aborted, code: ' + abortCode))
  })
}

Stream.prototype.setPriority = function setPriority (info) {
  var state = this._spdyState

  state.timeout.reset()

  if (!this._checkEnded()) {
    return
  }

  state.debug('id=%d priority change', this.id, info)

  var frame = { id: this.id, priority: info }

  // Change priority on this side
  this._handlePriority(frame)

  // And on the other too
  state.framer.priorityFrame(frame)
}

Stream.prototype.pushPromise = function pushPromise (uri, callback) {
  if (!this._checkEnded(callback)) {
    return
  }

  var self = this
  this._hardCork()
  var push = this.connection.pushPromise(this, uri, function (err) {
    self._hardUncork()
    if (!err) {
      push._hardUncork()
    }

    if (callback) {
      return callback(err, push)
    }

    if (err) { push.emit('error', err) }
  })
  push._hardCork()

  return push
}

Stream.prototype.setMaxChunk = function setMaxChunk (size) {
  var state = this._spdyState
  state.maxChunk = size
}

Stream.prototype.setTimeout = function setTimeout (delay, callback) {
  var state = this._spdyState

  state.timeout.set(delay, callback)
}


code:
'use strict'

var util = require('util')
var isNode = require('detect-node')

// Node.js 0.8, 0.10 and 0.12 support
Object.assign = (process.versions.modules >= 46 || !isNode)
  ? Object.assign // eslint-disable-next-line
  : util._extend

function QueueItem () {
  this.prev = null
  this.next = null
}
exports.QueueItem = QueueItem

function Queue () {
  QueueItem.call(this)

  this.prev = this
  this.next = this
}
util.inherits(Queue, QueueItem)
exports.Queue = Queue

Queue.prototype.insertTail = function insertTail (item) {
  item.prev = this.prev
  item.next = this
  item.prev.next = item
  item.next.prev = item
}

Queue.prototype.remove = function remove (item) {
  var next = item.next
  var prev = item.prev

  item.next = item
  item.prev = item
  next.prev = prev
  prev.next = next
}

Queue.prototype.head = function head () {
  return this.next
}

Queue.prototype.tail = function tail () {
  return this.prev
}

Queue.prototype.isEmpty = function isEmpty () {
  return this.next === this
}

Queue.prototype.isRoot = function isRoot (item) {
  return this === item
}

function LockStream (stream) {
  this.locked = false
  this.queue = []
  this.stream = stream
}
exports.LockStream = LockStream

LockStream.prototype.write = function write (chunks, callback) {
  var self = this

  // Do not let it interleave
  if (this.locked) {
    this.queue.push(function () {
      return self.write(chunks, callback)
    })
    return
  }

  this.locked = true

  function done (err, chunks) {
    self.stream.removeListener('error', done)

    self.locked = false
    if (self.queue.length > 0) { self.queue.shift()() }
    callback(err, chunks)
  }

  this.stream.on('error', done)

  // Accumulate all output data
  var output = []
  function onData (chunk) {
    output.push(chunk)
  }
  this.stream.on('data', onData)

  function next (err) {
    self.stream.removeListener('data', onData)
    if (err) {
      return done(err)
    }

    done(null, output)
  }

  for (var i = 0; i < chunks.length - 1; i++) { this.stream.write(chunks[i]) }

  if (chunks.length > 0) {
    this.stream.write(chunks[i], next)
  } else { process.nextTick(next) }

  if (this.stream.execute) {
    this.stream.execute(function (err) {
      if (err) { return done(err) }
    })
  }
}

// Just finds the place in array to insert
function binaryLookup (list, item, compare) {
  var start = 0
  var end = list.length

  while (start < end) {
    var pos = (start + end) >> 1
    var cmp = compare(item, list[pos])

    if (cmp === 0) {
      start = pos
      end = pos
      break
    } else if (cmp < 0) {
      end = pos
    } else {
      start = pos + 1
    }
  }

  return start
}
exports.binaryLookup = binaryLookup

function binaryInsert (list, item, compare) {
  var index = binaryLookup(list, item, compare)

  list.splice(index, 0, item)
}
exports.binaryInsert = binaryInsert

function binarySearch (list, item, compare) {
  var index = binaryLookup(list, item, compare)

  if (index >= list.length) {
    return -1
  }

  if (compare(item, list[index]) === 0) {
    return index
  }

  return -1
}
exports.binarySearch = binarySearch

function Timeout (object) {
  this.delay = 0
  this.timer = null
  this.object = object
}
exports.Timeout = Timeout

Timeout.prototype.set = function set (delay, callback) {
  this.delay = delay
  this.reset()
  if (!callback) { return }

  if (this.delay === 0) {
    this.object.removeListener('timeout', callback)
  } else {
    this.object.once('timeout', callback)
  }
}

Timeout.prototype.reset = function reset () {
  if (this.timer !== null) {
    clearTimeout(this.timer)
    this.timer = null
  }

  if (this.delay === 0) { return }

  var self = this
  this.timer = setTimeout(function () {
    self.timer = null
    self.object.emit('timeout')
  }, this.delay)
}


code:
'use strict'

var util = require('util')
var EventEmitter = require('events').EventEmitter
var debug = {
  server: require('debug')('spdy:window:server'),
  client: require('debug')('spdy:window:client')
}

function Side (window, name, options) {
  EventEmitter.call(this)

  this.name = name
  this.window = window
  this.current = options.size
  this.max = options.size
  this.limit = options.max
  this.lowWaterMark = options.lowWaterMark === undefined
    ? this.max / 2
    : options.lowWaterMark

  this._refilling = false
  this._refillQueue = []
}
util.inherits(Side, EventEmitter)

Side.prototype.setMax = function setMax (max) {
  this.window.debug('id=%d side=%s setMax=%d',
    this.window.id,
    this.name,
    max)
  this.max = max
  this.lowWaterMark = this.max / 2
}

Side.prototype.updateMax = function updateMax (max) {
  var delta = max - this.max
  this.window.debug('id=%d side=%s updateMax=%d delta=%d',
    this.window.id,
    this.name,
    max,
    delta)

  this.max = max
  this.lowWaterMark = max / 2

  this.update(delta)
}

Side.prototype.setLowWaterMark = function setLowWaterMark (lwm) {
  this.lowWaterMark = lwm
}

Side.prototype.update = function update (size, callback) {
  // Not enough space for the update, wait for refill
  if (size <= 0 && callback && this.isEmpty()) {
    this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]',
      this.window.id,
      this.name,
      -size,
      this.current,
      this.max)
    this._refillQueue.push({
      size: size,
      callback: callback
    })
    return
  }

  this.current += size

  if (this.current > this.limit) {
    this.emit('overflow')
    return
  }

  this.window.debug('id=%d side=%s update by=%d [%d/%d]',
    this.window.id,
    this.name,
    size,
    this.current,
    this.max)

  // Time to send WINDOW_UPDATE
  if (size < 0 && this.isDraining()) {
    this.window.debug('id=%d side=%s drained', this.window.id, this.name)
    this.emit('drain')
  }

  // Time to write
  if (size > 0 && this.current > 0 && this.current <= size) {
    this.window.debug('id=%d side=%s full', this.window.id, this.name)
    this.emit('full')
  }

  this._processRefillQueue()

  if (callback) { process.nextTick(callback) }
}

Side.prototype.getCurrent = function getCurrent () {
  return this.current
}

Side.prototype.getMax = function getMax () {
  return this.max
}

Side.prototype.getDelta = function getDelta () {
  return this.max - this.current
}

Side.prototype.isDraining = function isDraining () {
  return this.current <= this.lowWaterMark
}

Side.prototype.isEmpty = function isEmpty () {
  return this.current <= 0
}

// Private

Side.prototype._processRefillQueue = function _processRefillQueue () {
  // Prevent recursion
  if (this._refilling) {
    return
  }
  this._refilling = true

  while (this._refillQueue.length > 0) {
    var item = this._refillQueue[0]

    if (this.isEmpty()) {
      break
    }

    this.window.debug('id=%d side=%s refilled for size=%d',
      this.window.id,
      this.name,
      -item.size)

    this._refillQueue.shift()
    this.update(item.size, item.callback)
  }

  this._refilling = false
}

function Window (options) {
  this.id = options.id
  this.isServer = options.isServer
  this.debug = this.isServer ? debug.server : debug.client

  this.recv = new Side(this, 'recv', options.recv)
  this.send = new Side(this, 'send', options.send)
}
module.exports = Window

Window.prototype.clone = function clone (id) {
  return new Window({
    id: id,
    isServer: this.isServer,
    recv: {
      size: this.recv.max,
      max: this.recv.limit,
      lowWaterMark: this.recv.lowWaterMark
    },
    send: {
      size: this.send.max,
      max: this.send.limit,
      lowWaterMark: this.send.lowWaterMark
    }
  })
}


spdy-transport

main: lib/spdy-transport

description: SPDY v2, v3, v3.1 and HTTP2 transport

license: MIT

keywords: spdy http2 transport

type: git

author: Fedor Indutny <fedor@indutny.com>

lint: standard

test: mocha --reporter=spec test/**/*-test.js test/**/**/*-test.js

coverage: istanbul cover node_modules/.bin/_mocha -- --reporter=spec test/**/*-test.js test/**/**/*-test.js

pre-commit: lint test


code:
sudo: false

language: node_js

node_js:
  - "6"
  - "8"
  - "10"

script:
  - npm run lint
  - npm test
  - npm run coverage


code:
# SPDY Server for node.js

[![Build Status](https://travis-ci.org/spdy-http2/node-spdy.svg?branch=master)](http://travis-ci.org/spdy-http2/node-spdy)
[![NPM version](https://badge.fury.io/js/spdy.svg)](http://badge.fury.io/js/spdy)
[![dependencies Status](https://david-dm.org/spdy-http2/node-spdy/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/node-spdy)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/)
[![Waffle](https://img.shields.io/badge/track-waffle-blue.svg?style=flat-square)](https://waffle.io/spdy-http2/node-spdy)

With this module you can create [HTTP2][0] / [SPDY][1] servers
in node.js with natural http module interface and fallback to regular https
(for browsers that don't support neither HTTP2, nor SPDY yet).

This module named `spdy` but it [provides](https://github.com/indutny/node-spdy/issues/269#issuecomment-239014184) support for both http/2 (h2) and spdy (2,3,3.1). Also, `spdy` is compatible with Express.

## Usage

### Examples

Server:
```javascript
var spdy = require('spdy'),
    fs = require('fs');

var options = {
  // Private key
  key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),

  // Fullchain file or cert file (prefer the former)
  cert: fs.readFileSync(__dirname + '/keys/spdy-fullchain.pem'),

  // **optional** SPDY-specific options
  spdy: {
    protocols: [ 'h2', 'spdy/3.1', ..., 'http/1.1' ],
    plain: false,

    // **optional**
    // Parse first incoming X_FORWARDED_FOR frame and put it to the
    // headers of every request.
    // NOTE: Use with care! This should not be used without some proxy that
    // will *always* send X_FORWARDED_FOR
    'x-forwarded-for': true,

    connection: {
      windowSize: 1024 * 1024, // Server's window size

      // **optional** if true - server will send 3.1 frames on 3.0 *plain* spdy
      autoSpdy31: false
    }
  }
};

var server = spdy.createServer(options, function(req, res) {
  res.writeHead(200);
  res.end('hello world!');
});

server.listen(3000);
```

Client:
```javascript
var spdy = require('spdy');
var https = require('https');

var agent = spdy.createAgent({
  host: 'www.google.com',
  port: 443,

  // Optional SPDY options
  spdy: {
    plain: false,
    ssl: true,

    // **optional** send X_FORWARDED_FOR
    'x-forwarded-for': '127.0.0.1'
  }
});

https.get({
  host: 'www.google.com',
  agent: agent
}, function(response) {
  console.log('yikes');
  // Here it goes like with any other node.js HTTP request
  // ...
  // And once we're done - we may close TCP connection to server
  // NOTE: All non-closed requests will die!
  agent.close();
}).end();
```

Please note that if you use a custom agent, by default all connection-level
errors will result in an uncaught exception. To handle these errors subscribe
to the `error` event and re-emit the captured error:

```javascript
var agent = spdy.createAgent({
  host: 'www.google.com',
  port: 443
}).once('error', function (err) {
  this.emit(err);
});
```

#### Push streams

It is possible to initiate [PUSH_PROMISE][5] to send content to clients _before_
the client requests it.

```javascript
spdy.createServer(options, function(req, res) {
  var stream = res.push('/main.js', {
    status: 200, // optional
    method: 'GET', // optional
    request: {
      accept: '*/*'
    },
    response: {
      'content-type': 'application/javascript'
    }
  });
  stream.on('error', function() {
  });
  stream.end('alert("hello from push stream!");');

  res.end('<script src="/main.js"></script>');
}).listen(3000);
```

[PUSH_PROMISE][5] may be sent using the `push()` method on the current response
object.  The signature of the `push()` method is:

`.push('/some/relative/url', { request: {...}, response: {...} }, callback)`

Second argument contains headers for both PUSH_PROMISE and emulated response.
`callback` will receive two arguments: `err` (if any error is happened) and a
[Duplex][4] stream as the second argument.

Client usage:
```javascript
var agent = spdy.createAgent({ /* ... */ });
var req = http.get({
  host: 'www.google.com',
  agent: agent
}, function(response) {
});
req.on('push', function(stream) {
  stream.on('error', function(err) {
    // Handle error
  });
  // Read data from stream
});
```

NOTE: You're responsible for the `stream` object once given it in `.push()`
callback or `push` event. Hence ignoring `error` event on it will result in
uncaught exception and crash your program.

#### Trailing headers

Server usage:
```javascript
function (req, res) {
  // Send trailing headers to client
  res.addTrailers({ header1: 'value1', header2: 'value2' });

  // On client's trailing headers
  req.on('trailers', function(headers) {
    // ...
  });
}
```

Client usage:
```javascript
var req = http.request({ agent: spdyAgent, /* ... */ }).function (res) {
  // On server's trailing headers
  res.on('trailers', function(headers) {
    // ...
  });
});
req.write('stuff');
req.addTrailers({ /* ... */ });
req.end();
```

#### Options

All options supported by [tls][2] work with node-spdy.

Additional options may be passed via `spdy` sub-object:

* `plain` - if defined, server will ignore NPN and ALPN data and choose whether
  to use spdy or plain http by looking at first data packet.
* `ssl` - if `false` and `options.plain` is `true`, `http.Server` will be used
  as a `base` class for created server.
* `maxChunk` - if set and non-falsy, limits number of bytes sent in one DATA
  chunk. Setting it to non-zero value is recommended if you care about
  interleaving of outgoing data from multiple different streams.
  (defaults to 8192)
* `protocols` - list of NPN/ALPN protocols to use (default is:
  `['h2','spdy/3.1', 'spdy/3', 'spdy/2','http/1.1', 'http/1.0']`)
* `protocol` - use specific protocol if no NPN/ALPN ex In addition,
* `maxStreams` - set "[maximum concurrent streams][3]" protocol option

### API

API is compatible with `http` and `https` module, but you can use another
function as base class for SPDYServer.

```javascript
spdy.createServer(
  [base class constructor, i.e. https.Server],
  { /* keys and options */ }, // <- the only one required argument
  [request listener]
).listen([port], [host], [callback]);
```

Request listener will receive two arguments: `request` and `response`. They're
both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But three
custom properties are added to both of them: `isSpdy`, `spdyVersion`. `isSpdy`
is `true` when the request was processed using HTTP2/SPDY protocols, it is
`false` in case of HTTP/1.1 fallback. `spdyVersion` is either of: `2`, `3`,
`3.1`, or `4` (for HTTP2).

#### Contributors

* [Fedor Indutny](https://github.com/indutny)
* [Chris Strom](https://github.com/eee-c)
* [François de Metz](https://github.com/francois2metz)
* [Ilya Grigorik](https://github.com/igrigorik)
* [Roberto Peon](https://github.com/grmocg)
* [Tatsuhiro Tsujikawa](https://github.com/tatsuhiro-t)
* [Jesse Cravens](https://github.com/jessecravens)

#### LICENSE

This software is licensed under the MIT License.

Copyright Fedor Indutny, 2015.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

[0]: https://http2.github.io/
[1]: http://www.chromium.org/spdy
[2]: http://nodejs.org/docs/latest/api/tls.html#tls.createServer
[3]: https://httpwg.github.io/specs/rfc7540.html#SETTINGS_MAX_CONCURRENT_STREAMS
[4]: https://iojs.org/api/stream.html#stream_class_stream_duplex
[5]: https://httpwg.github.io/specs/rfc7540.html#PUSH_PROMISE


code:
'use strict'

var spdy = exports

// Export tools
spdy.handle = require('./spdy/handle')
spdy.request = require('./spdy/request')
spdy.response = require('./spdy/response')
spdy.Socket = require('./spdy/socket')

// Export client
spdy.agent = require('./spdy/agent')
spdy.Agent = spdy.agent.Agent
spdy.createAgent = spdy.agent.create

// Export server
spdy.server = require('./spdy/server')
spdy.Server = spdy.server.Server
spdy.PlainServer = spdy.server.PlainServer
spdy.createServer = spdy.server.create


code:
'use strict'

var assert = require('assert')
var http = require('http')
var https = require('https')
var net = require('net')
var util = require('util')
var transport = require('spdy-transport')
var debug = require('debug')('spdy:client')

// Node.js 0.10 and 0.12 support
Object.assign = process.versions.modules >= 46
  ? Object.assign // eslint-disable-next-line
  : util._extend

var EventEmitter = require('events').EventEmitter

var spdy = require('../spdy')

var mode = /^v0\.8\./.test(process.version)
  ? 'rusty'
  : /^v0\.(9|10)\./.test(process.version)
    ? 'old'
    : /^v0\.12\./.test(process.version)
      ? 'normal'
      : 'modern'

var proto = {}

function instantiate (base) {
  function Agent (options) {
    this._init(base, options)
  }
  util.inherits(Agent, base)

  Agent.create = function create (options) {
    return new Agent(options)
  }

  Object.keys(proto).forEach(function (key) {
    Agent.prototype[key] = proto[key]
  })

  return Agent
}

proto._init = function _init (base, options) {
  base.call(this, options)

  var state = {}
  this._spdyState = state

  state.host = options.host
  state.options = options.spdy || {}
  state.secure = this instanceof https.Agent
  state.fallback = false
  state.createSocket = this._getCreateSocket()
  state.socket = null
  state.connection = null

  // No chunked encoding
  this.keepAlive = false

  var self = this
  this._connect(options, function (err, connection) {
    if (err) {
      return self.emit('error', err)
    }

    state.connection = connection
    self.emit('_connect')
  })
}

proto._getCreateSocket = function _getCreateSocket () {
  // Find super's `createSocket` method
  var createSocket
  var cons = this.constructor.super_
  do {
    createSocket = cons.prototype.createSocket

    if (cons.super_ === EventEmitter || !cons.super_) {
      break
    }
    cons = cons.super_
  } while (!createSocket)
  if (!createSocket) {
    createSocket = http.Agent.prototype.createSocket
  }

  assert(createSocket, '.createSocket() method not found')

  return createSocket
}

proto._connect = function _connect (options, callback) {
  var self = this
  var state = this._spdyState

  var protocols = state.options.protocols || [
    'h2',
    'spdy/3.1', 'spdy/3', 'spdy/2',
    'http/1.1', 'http/1.0'
  ]

  // TODO(indutny): reconnect automatically?
  var socket = this.createConnection(Object.assign({
    NPNProtocols: protocols,
    ALPNProtocols: protocols,
    servername: options.servername || options.host
  }, options))
  state.socket = socket

  socket.setNoDelay(true)

  function onError (err) {
    return callback(err)
  }
  socket.on('error', onError)

  socket.on(state.secure ? 'secureConnect' : 'connect', function () {
    socket.removeListener('error', onError)

    var protocol
    if (state.secure) {
      protocol = socket.npnProtocol ||
                 socket.alpnProtocol ||
                 state.options.protocol
    } else {
      protocol = state.options.protocol
    }

    // HTTP server - kill socket and switch to the fallback mode
    if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') {
      debug('activating fallback')
      socket.destroy()
      state.fallback = true
      return
    }

    debug('connected protocol=%j', protocol)
    var connection = transport.connection.create(socket, Object.assign({
      protocol: /spdy/.test(protocol) ? 'spdy' : 'http2',
      isServer: false
    }, state.options.connection || {}))

    // Pass connection level errors are passed to the agent.
    connection.on('error', function (err) {
      self.emit('error', err)
    })

    // Set version when we are certain
    if (protocol === 'h2') {
      connection.start(4)
    } else if (protocol === 'spdy/3.1') {
      connection.start(3.1)
    } else if (protocol === 'spdy/3') {
      connection.start(3)
    } else if (protocol === 'spdy/2') {
      connection.start(2)
    } else {
      socket.destroy()
      callback(new Error('Unexpected protocol: ' + protocol))
      return
    }

    if (state.options['x-forwarded-for'] !== undefined) {
      connection.sendXForwardedFor(state.options['x-forwarded-for'])
    }

    callback(null, connection)
  })
}

proto._createSocket = function _createSocket (req, options, callback) {
  var state = this._spdyState
  if (state.fallback) { return state.createSocket(req, options) }

  var handle = spdy.handle.create(null, null, state.socket)

  var socketOptions = {
    handle: handle,
    allowHalfOpen: true
  }

  var socket
  if (state.secure) {
    socket = new spdy.Socket(state.socket, socketOptions)
  } else {
    socket = new net.Socket(socketOptions)
  }

  handle.assignSocket(socket)
  handle.assignClientRequest(req)

  // Create stream only once `req.end()` is called
  var self = this
  handle.once('needStream', function () {
    if (state.connection === null) {
      self.once('_connect', function () {
        handle.setStream(self._createStream(req, handle))
      })
    } else {
      handle.setStream(self._createStream(req, handle))
    }
  })

  // Yes, it is in reverse
  req.on('response', function (res) {
    handle.assignRequest(res)
  })
  handle.assignResponse(req)

  // Handle PUSH
  req.addListener('newListener', spdy.request.onNewListener)

  // For v0.8
  socket.readable = true
  socket.writable = true

  if (callback) {
    return callback(null, socket)
  }

  return socket
}

if (mode === 'modern' || mode === 'normal') {
  proto.createSocket = proto._createSocket
} else {
  proto.createSocket = function createSocket (name, host, port, addr, req) {
    var state = this._spdyState
    if (state.fallback) {
      return state.createSocket(name, host, port, addr, req)
    }

    return this._createSocket(req, {
      host: host,
      port: port
    })
  }
}

proto._createStream = function _createStream (req, handle) {
  var state = this._spdyState

  var self = this
  return state.connection.reserveStream({
    method: req.method,
    path: req.path,
    headers: req._headers,
    host: state.host
  }, function (err, stream) {
    if (err) {
      return self.emit('error', err)
    }

    stream.on('response', function (status, headers) {
      handle.emitResponse(status, headers)
    })
  })
}

// Public APIs

proto.close = function close (callback) {
  var state = this._spdyState

  if (state.connection === null) {
    this.once('_connect', function () {
      this.close(callback)
    })
    return
  }

  state.connection.end(callback)
}

exports.Agent = instantiate(https.Agent)
exports.PlainAgent = instantiate(http.Agent)

exports.create = function create (base, options) {
  if (typeof base === 'object') {
    options = base
    base = null
  }

  if (base) {
    return instantiate(base).create(options)
  }

  if (options.spdy && options.spdy.plain) {
    return exports.PlainAgent.create(options)
  } else { return exports.Agent.create(options) }
}