code:
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
 * Copyright 2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE or:
 * http://opensource.org/licenses/BSD-3-Clause
 */

/**
 * This is a helper function for getting values from parameter/options
 * objects.
 *
 * @param args The object we are extracting values from
 * @param name The name of the property we are getting.
 * @param defaultValue An optional value to return if the property is missing
 * from the object. If this is not specified and the property is missing, an
 * error will be thrown.
 */
function getArg(aArgs, aName, aDefaultValue) {
  if (aName in aArgs) {
    return aArgs[aName];
  } else if (arguments.length === 3) {
    return aDefaultValue;
  } else {
    throw new Error('"' + aName + '" is a required argument.');
  }
}
exports.getArg = getArg;

var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;
var dataUrlRegexp = /^data:.+\,.+$/;

function urlParse(aUrl) {
  var match = aUrl.match(urlRegexp);
  if (!match) {
    return null;
  }
  return {
    scheme: match[1],
    auth: match[2],
    host: match[3],
    port: match[4],
    path: match[5]
  };
}
exports.urlParse = urlParse;

function urlGenerate(aParsedUrl) {
  var url = '';
  if (aParsedUrl.scheme) {
    url += aParsedUrl.scheme + ':';
  }
  url += '//';
  if (aParsedUrl.auth) {
    url += aParsedUrl.auth + '@';
  }
  if (aParsedUrl.host) {
    url += aParsedUrl.host;
  }
  if (aParsedUrl.port) {
    url += ":" + aParsedUrl.port
  }
  if (aParsedUrl.path) {
    url += aParsedUrl.path;
  }
  return url;
}
exports.urlGenerate = urlGenerate;

/**
 * Normalizes a path, or the path portion of a URL:
 *
 * - Replaces consecutive slashes with one slash.
 * - Removes unnecessary '.' parts.
 * - Removes unnecessary '<dir>/..' parts.
 *
 * Based on code in the Node.js 'path' core module.
 *
 * @param aPath The path or url to normalize.
 */
function normalize(aPath) {
  var path = aPath;
  var url = urlParse(aPath);
  if (url) {
    if (!url.path) {
      return aPath;
    }
    path = url.path;
  }
  var isAbsolute = exports.isAbsolute(path);

  var parts = path.split(/\/+/);
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
    part = parts[i];
    if (part === '.') {
      parts.splice(i, 1);
    } else if (part === '..') {
      up++;
    } else if (up > 0) {
      if (part === '') {
        // The first part is blank if the path is absolute. Trying to go
        // above the root is a no-op. Therefore we can remove all '..' parts
        // directly after the root.
        parts.splice(i + 1, up);
        up = 0;
      } else {
        parts.splice(i, 2);
        up--;
      }
    }
  }
  path = parts.join('/');

  if (path === '') {
    path = isAbsolute ? '/' : '.';
  }

  if (url) {
    url.path = path;
    return urlGenerate(url);
  }
  return path;
}
exports.normalize = normalize;

/**
 * Joins two paths/URLs.
 *
 * @param aRoot The root path or URL.
 * @param aPath The path or URL to be joined with the root.
 *
 * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
 *   scheme-relative URL: Then the scheme of aRoot, if any, is prepended
 *   first.
 * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
 *   is updated with the result and aRoot is returned. Otherwise the result
 *   is returned.
 *   - If aPath is absolute, the result is aPath.
 *   - Otherwise the two paths are joined with a slash.
 * - Joining for example 'http://' and 'www.example.com' is also supported.
 */
function join(aRoot, aPath) {
  if (aRoot === "") {
    aRoot = ".";
  }
  if (aPath === "") {
    aPath = ".";
  }
  var aPathUrl = urlParse(aPath);
  var aRootUrl = urlParse(aRoot);
  if (aRootUrl) {
    aRoot = aRootUrl.path || '/';
  }

  // `join(foo, '//www.example.org')`
  if (aPathUrl && !aPathUrl.scheme) {
    if (aRootUrl) {
      aPathUrl.scheme = aRootUrl.scheme;
    }
    return urlGenerate(aPathUrl);
  }

  if (aPathUrl || aPath.match(dataUrlRegexp)) {
    return aPath;
  }

  // `join('http://', 'www.example.com')`
  if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
    aRootUrl.host = aPath;
    return urlGenerate(aRootUrl);
  }

  var joined = aPath.charAt(0) === '/'
    ? aPath
    : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);

  if (aRootUrl) {
    aRootUrl.path = joined;
    return urlGenerate(aRootUrl);
  }
  return joined;
}
exports.join = join;

exports.isAbsolute = function (aPath) {
  return aPath.charAt(0) === '/' || urlRegexp.test(aPath);
};

/**
 * Make a path relative to a URL or another path.
 *
 * @param aRoot The root path or URL.
 * @param aPath The path or URL to be made relative to aRoot.
 */
function relative(aRoot, aPath) {
  if (aRoot === "") {
    aRoot = ".";
  }

  aRoot = aRoot.replace(/\/$/, '');

  // It is possible for the path to be above the root. In this case, simply
  // checking whether the root is a prefix of the path won't work. Instead, we
  // need to remove components from the root one by one, until either we find
  // a prefix that fits, or we run out of components to remove.
  var level = 0;
  while (aPath.indexOf(aRoot + '/') !== 0) {
    var index = aRoot.lastIndexOf("/");
    if (index < 0) {
      return aPath;
    }

    // If the only part of the root that is left is the scheme (i.e. http://,
    // file:///, etc.), one or more slashes (/), or simply nothing at all, we
    // have exhausted all components, so the path is not relative to the root.
    aRoot = aRoot.slice(0, index);
    if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
      return aPath;
    }

    ++level;
  }

  // Make sure we add a "../" for each component we removed from the root.
  return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
}
exports.relative = relative;

var supportsNullProto = (function () {
  var obj = Object.create(null);
  return !('__proto__' in obj);
}());

function identity (s) {
  return s;
}

/**
 * Because behavior goes wacky when you set `__proto__` on objects, we
 * have to prefix all the strings in our set with an arbitrary character.
 *
 * See https://github.com/mozilla/source-map/pull/31 and
 * https://github.com/mozilla/source-map/issues/30
 *
 * @param String aStr
 */
function toSetString(aStr) {
  if (isProtoString(aStr)) {
    return '$' + aStr;
  }

  return aStr;
}
exports.toSetString = supportsNullProto ? identity : toSetString;

function fromSetString(aStr) {
  if (isProtoString(aStr)) {
    return aStr.slice(1);
  }

  return aStr;
}
exports.fromSetString = supportsNullProto ? identity : fromSetString;

function isProtoString(s) {
  if (!s) {
    return false;
  }

  var length = s.length;

  if (length < 9 /* "__proto__".length */) {
    return false;
  }

  if (s.charCodeAt(length - 1) !== 95  /* '_' */ ||
      s.charCodeAt(length - 2) !== 95  /* '_' */ ||
      s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
      s.charCodeAt(length - 4) !== 116 /* 't' */ ||
      s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
      s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
      s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
      s.charCodeAt(length - 8) !== 95  /* '_' */ ||
      s.charCodeAt(length - 9) !== 95  /* '_' */) {
    return false;
  }

  for (var i = length - 10; i >= 0; i--) {
    if (s.charCodeAt(i) !== 36 /* '$' */) {
      return false;
    }
  }

  return true;
}

/**
 * Comparator between two mappings where the original positions are compared.
 *
 * Optionally pass in `true` as `onlyCompareGenerated` to consider two
 * mappings with the same original source/line/column, but different generated
 * line and column the same. Useful when searching for a mapping with a
 * stubbed out mapping.
 */
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
  var cmp = strcmp(mappingA.source, mappingB.source);
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalLine - mappingB.originalLine;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalColumn - mappingB.originalColumn;
  if (cmp !== 0 || onlyCompareOriginal) {
    return cmp;
  }

  cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.generatedLine - mappingB.generatedLine;
  if (cmp !== 0) {
    return cmp;
  }

  return strcmp(mappingA.name, mappingB.name);
}
exports.compareByOriginalPositions = compareByOriginalPositions;

/**
 * Comparator between two mappings with deflated source and name indices where
 * the generated positions are compared.
 *
 * Optionally pass in `true` as `onlyCompareGenerated` to consider two
 * mappings with the same generated line and column, but different
 * source/name/original line and column the same. Useful when searching for a
 * mapping with a stubbed out mapping.
 */
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
  var cmp = mappingA.generatedLine - mappingB.generatedLine;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  if (cmp !== 0 || onlyCompareGenerated) {
    return cmp;
  }

  cmp = strcmp(mappingA.source, mappingB.source);
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalLine - mappingB.originalLine;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalColumn - mappingB.originalColumn;
  if (cmp !== 0) {
    return cmp;
  }

  return strcmp(mappingA.name, mappingB.name);
}
exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;

function strcmp(aStr1, aStr2) {
  if (aStr1 === aStr2) {
    return 0;
  }

  if (aStr1 === null) {
    return 1; // aStr2 !== null
  }

  if (aStr2 === null) {
    return -1; // aStr1 !== null
  }

  if (aStr1 > aStr2) {
    return 1;
  }

  return -1;
}

/**
 * Comparator between two mappings with inflated source and name strings where
 * the generated positions are compared.
 */
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
  var cmp = mappingA.generatedLine - mappingB.generatedLine;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.generatedColumn - mappingB.generatedColumn;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = strcmp(mappingA.source, mappingB.source);
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalLine - mappingB.originalLine;
  if (cmp !== 0) {
    return cmp;
  }

  cmp = mappingA.originalColumn - mappingB.originalColumn;
  if (cmp !== 0) {
    return cmp;
  }

  return strcmp(mappingA.name, mappingB.name);
}
exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;

/**
 * Strip any JSON XSSI avoidance prefix from the string (as documented
 * in the source maps specification), and then parse the string as
 * JSON.
 */
function parseSourceMapInput(str) {
  return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, ''));
}
exports.parseSourceMapInput = parseSourceMapInput;

/**
 * Compute the URL of a source given the the source root, the source's
 * URL, and the source map's URL.
 */
function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) {
  sourceURL = sourceURL || '';

  if (sourceRoot) {
    // This follows what Chrome does.
    if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') {
      sourceRoot += '/';
    }
    // The spec says:
    //   Line 4: An optional source root, useful for relocating source
    //   files on a server or removing repeated values in the
    //   “sources” entry.  This value is prepended to the individual
    //   entries in the “source” field.
    sourceURL = sourceRoot + sourceURL;
  }

  // Historically, SourceMapConsumer did not take the sourceMapURL as
  // a parameter.  This mode is still somewhat supported, which is why
  // this code block is conditional.  However, it's preferable to pass
  // the source map URL to SourceMapConsumer, so that this function
  // can implement the source URL resolution algorithm as outlined in
  // the spec.  This block is basically the equivalent of:
  //    new URL(sourceURL, sourceMapURL).toString()
  // ... except it avoids using URL, which wasn't available in the
  // older releases of node still supported by this library.
  //
  // The spec says:
  //   If the sources are not absolute URLs after prepending of the
  //   “sourceRoot”, the sources are resolved relative to the
  //   SourceMap (like resolving script src in a html document).
  if (sourceMapURL) {
    var parsed = urlParse(sourceMapURL);
    if (!parsed) {
      throw new Error("sourceMapURL could not be parsed");
    }
    if (parsed.path) {
      // Strip the last path component, but keep the "/".
      var index = parsed.path.lastIndexOf('/');
      if (index >= 0) {
        parsed.path = parsed.path.substring(0, index + 1);
      }
    }
    sourceURL = join(urlGenerate(parsed), sourceURL);
  }

  return normalize(sourceURL);
}
exports.computeSourceURL = computeSourceURL;


source-map

description: Generates and consumes source maps

type: git

test: npm run build && node test/run-tests.js

build: webpack --color

toc: doctoc --title '## Table of Contents' README.md && doctoc --title '## Table of Contents' CONTRIBUTING.md

typings: source-map

license: mit


code:
export interface StartOfSourceMap {
    file?: string;
    sourceRoot?: string;
}

export interface RawSourceMap extends StartOfSourceMap {
    version: string;
    sources: string[];
    names: string[];
    sourcesContent?: string[];
    mappings: string;
}

export interface Position {
    line: number;
    column: number;
}

export interface LineRange extends Position {
    lastColumn: number;
}

export interface FindPosition extends Position {
    // SourceMapConsumer.GREATEST_LOWER_BOUND or SourceMapConsumer.LEAST_UPPER_BOUND
    bias?: number;
}

export interface SourceFindPosition extends FindPosition {
    source: string;
}

export interface MappedPosition extends Position {
    source: string;
    name?: string;
}

export interface MappingItem {
    source: string;
    generatedLine: number;
    generatedColumn: number;
    originalLine: number;
    originalColumn: number;
    name: string;
}

export class SourceMapConsumer {
    static GENERATED_ORDER: number;
    static ORIGINAL_ORDER: number;

    static GREATEST_LOWER_BOUND: number;
    static LEAST_UPPER_BOUND: number;

    constructor(rawSourceMap: RawSourceMap);
    computeColumnSpans(): void;
    originalPositionFor(generatedPosition: FindPosition): MappedPosition;
    generatedPositionFor(originalPosition: SourceFindPosition): LineRange;
    allGeneratedPositionsFor(originalPosition: MappedPosition): Position[];
    hasContentsOfAllSources(): boolean;
    sourceContentFor(source: string, returnNullOnMissing?: boolean): string;
    eachMapping(callback: (mapping: MappingItem) => void, context?: any, order?: number): void;
}

export interface Mapping {
    generated: Position;
    original: Position;
    source: string;
    name?: string;
}

export class SourceMapGenerator {
    constructor(startOfSourceMap?: StartOfSourceMap);
    static fromSourceMap(sourceMapConsumer: SourceMapConsumer): SourceMapGenerator;
    addMapping(mapping: Mapping): void;
    setSourceContent(sourceFile: string, sourceContent: string): void;
    applySourceMap(sourceMapConsumer: SourceMapConsumer, sourceFile?: string, sourceMapPath?: string): void;
    toString(): string;
}

export interface CodeWithSourceMap {
    code: string;
    map: SourceMapGenerator;
}

export class SourceNode {
    constructor();
    constructor(line: number, column: number, source: string);
    constructor(line: number, column: number, source: string, chunk?: string, name?: string);
    static fromStringWithSourceMap(code: string, sourceMapConsumer: SourceMapConsumer, relativePath?: string): SourceNode;
    add(chunk: string): void;
    prepend(chunk: string): void;
    setSourceContent(sourceFile: string, sourceContent: string): void;
    walk(fn: (chunk: string, mapping: MappedPosition) => void): void;
    walkSourceContents(fn: (file: string, content: string) => void): void;
    join(sep: string): SourceNode;
    replaceRight(pattern: string, replacement: string): SourceNode;
    toString(): string;
    toStringWithSourceMap(startOfSourceMap?: StartOfSourceMap): CodeWithSourceMap;
}


code:
/*
 * Copyright 2009-2011 Mozilla Foundation and contributors
 * Licensed under the New BSD license. See LICENSE.txt or:
 * http://opensource.org/licenses/BSD-3-Clause
 */
exports.SourceMapGenerator = require('./lib/source-map-generator').SourceMapGenerator;
exports.SourceMapConsumer = require('./lib/source-map-consumer').SourceMapConsumer;
exports.SourceNode = require('./lib/source-node').SourceNode;


code:
## Usage

```javascript
var correct = require('spdx-correct')
var assert = require('assert')

assert.strictEqual(correct('mit'), 'MIT')

assert.strictEqual(correct('Apache 2'), 'Apache-2.0')

assert(correct('No idea what license') === null)

// disable upgrade option
assert(correct('GPL-3.0'), 'GPL-3.0-or-later')
assert(correct('GPL-3.0', { upgrade: false }), 'GPL-3.0')
```

## Contributors

spdx-correct has benefited from the work of several contributors.
See [the GitHub repository](https://github.com/jslicense/spdx-correct.js/graphs/contributors)
for more information.


code:
/*
Copyright spdx-correct.js contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var parse = require('spdx-expression-parse')
var spdxLicenseIds = require('spdx-license-ids')

function valid (string) {
  try {
    parse(string)
    return true
  } catch (error) {
    return false
  }
}

// Sorting function that orders the given array of transpositions such
// that a transposition with the longer pattern comes before a transposition
// with a shorter pattern. This is to prevent e.g. the transposition
// ["General Public License", "GPL"] from matching to "Lesser General Public License"
// before a longer and more accurate transposition ["Lesser General Public License", "LGPL"]
// has a chance to be recognized.
function sortTranspositions(a, b) {
  var length = b[0].length - a[0].length
  if (length !== 0) return length
  return a[0].toUpperCase().localeCompare(b[0].toUpperCase())
}

// Common transpositions of license identifier acronyms
var transpositions = [
  ['APGL', 'AGPL'],
  ['Gpl', 'GPL'],
  ['GLP', 'GPL'],
  ['APL', 'Apache'],
  ['ISD', 'ISC'],
  ['GLP', 'GPL'],
  ['IST', 'ISC'],
  ['Claude', 'Clause'],
  [' or later', '+'],
  [' International', ''],
  ['GNU', 'GPL'],
  ['GUN', 'GPL'],
  ['+', ''],
  ['GNU GPL', 'GPL'],
  ['GNU LGPL', 'LGPL'],
  ['GNU/GPL', 'GPL'],
  ['GNU GLP', 'GPL'],
  ['GNU LESSER GENERAL PUBLIC LICENSE', 'LGPL'],
  ['GNU Lesser General Public License', 'LGPL'],
  ['GNU LESSER GENERAL PUBLIC LICENSE', 'LGPL-2.1'],
  ['GNU Lesser General Public License', 'LGPL-2.1'],
  ['LESSER GENERAL PUBLIC LICENSE', 'LGPL'],
  ['Lesser General Public License', 'LGPL'],
  ['LESSER GENERAL PUBLIC LICENSE', 'LGPL-2.1'],
  ['Lesser General Public License', 'LGPL-2.1'],
  ['GNU General Public License', 'GPL'],
  ['Gnu public license', 'GPL'],
  ['GNU Public License', 'GPL'],
  ['GNU GENERAL PUBLIC LICENSE', 'GPL'],
  ['MTI', 'MIT'],
  ['Mozilla Public License', 'MPL'],
  ['Universal Permissive License', 'UPL'],
  ['WTH', 'WTF'],
  ['WTFGPL', 'WTFPL'],
  ['-License', '']
].sort(sortTranspositions)

var TRANSPOSED = 0
var CORRECT = 1

// Simple corrections to nearly valid identifiers.
var transforms = [
  // e.g. 'mit'
  function (argument) {
    return argument.toUpperCase()
  },
  // e.g. 'MIT '
  function (argument) {
    return argument.trim()
  },
  // e.g. 'M.I.T.'
  function (argument) {
    return argument.replace(/\./g, '')
  },
  // e.g. 'Apache- 2.0'
  function (argument) {
    return argument.replace(/\s+/g, '')
  },
  // e.g. 'CC BY 4.0''
  function (argument) {
    return argument.replace(/\s+/g, '-')
  },
  // e.g. 'LGPLv2.1'
  function (argument) {
    return argument.replace('v', '-')
  },
  // e.g. 'Apache 2.0'
  function (argument) {
    return argument.replace(/,?\s*(\d)/, '-$1')
  },
  // e.g. 'GPL 2'
  function (argument) {
    return argument.replace(/,?\s*(\d)/, '-$1.0')
  },
  // e.g. 'Apache Version 2.0'
  function (argument) {
    return argument
      .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2')
  },
  // e.g. 'Apache Version 2'
  function (argument) {
    return argument
      .replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0')
  },
  // e.g. 'ZLIB'
  function (argument) {
    return argument[0].toUpperCase() + argument.slice(1)
  },
  // e.g. 'MPL/2.0'
  function (argument) {
    return argument.replace('/', '-')
  },
  // e.g. 'Apache 2'
  function (argument) {
    return argument
      .replace(/\s*V\s*(\d)/, '-$1')
      .replace(/(\d)$/, '$1.0')
  },
  // e.g. 'GPL-2.0', 'GPL-3.0'
  function (argument) {
    if (argument.indexOf('3.0') !== -1) {
      return argument + '-or-later'
    } else {
      return argument + '-only'
    }
  },
  // e.g. 'GPL-2.0-'
  function (argument) {
    return argument + 'only'
  },
  // e.g. 'GPL2'
  function (argument) {
    return argument.replace(/(\d)$/, '-$1.0')
  },
  // e.g. 'BSD 3'
  function (argument) {
    return argument.replace(/(-| )?(\d)$/, '-$2-Clause')
  },
  // e.g. 'BSD clause 3'
  function (argument) {
    return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause')
  },
  // e.g. 'New BSD license'
  function (argument) {
    return argument.replace(/\b(Modified|New|Revised)(-| )?BSD((-| )License)?/i, 'BSD-3-Clause')
  },
  // e.g. 'Simplified BSD license'
  function (argument) {
    return argument.replace(/\bSimplified(-| )?BSD((-| )License)?/i, 'BSD-2-Clause')
  },
  // e.g. 'Free BSD license'
  function (argument) {
    return argument.replace(/\b(Free|Net)(-| )?BSD((-| )License)?/i, 'BSD-2-Clause-$1BSD')
  },
  // e.g. 'Clear BSD license'
  function (argument) {
    return argument.replace(/\bClear(-| )?BSD((-| )License)?/i, 'BSD-3-Clause-Clear')
  },
  // e.g. 'Old BSD License'
  function (argument) {
    return argument.replace(/\b(Old|Original)(-| )?BSD((-| )License)?/i, 'BSD-4-Clause')
  },
  // e.g. 'BY-NC-4.0'
  function (argument) {
    return 'CC-' + argument
  },
  // e.g. 'BY-NC'
  function (argument) {
    return 'CC-' + argument + '-4.0'
  },
  // e.g. 'Attribution-NonCommercial'
  function (argument) {
    return argument
      .replace('Attribution', 'BY')
      .replace('NonCommercial', 'NC')
      .replace('NoDerivatives', 'ND')
      .replace(/ (\d)/, '-$1')
      .replace(/ ?International/, '')
  },
  // e.g. 'Attribution-NonCommercial'
  function (argument) {
    return 'CC-' +
      argument
        .replace('Attribution', 'BY')
        .replace('NonCommercial', 'NC')
        .replace('NoDerivatives', 'ND')
        .replace(/ (\d)/, '-$1')
        .replace(/ ?International/, '') +
      '-4.0'
  }
]

var licensesWithVersions = spdxLicenseIds
  .map(function (id) {
    var match = /^(.*)-\d+\.\d+$/.exec(id)
    return match
      ? [match[0], match[1]]
      : [id, null]
  })
  .reduce(function (objectMap, item) {
    var key = item[1]
    objectMap[key] = objectMap[key] || []
    objectMap[key].push(item[0])
    return objectMap
  }, {})

var licensesWithOneVersion = Object.keys(licensesWithVersions)
  .map(function makeEntries (key) {
    return [key, licensesWithVersions[key]]
  })
  .filter(function identifySoleVersions (item) {
    return (
      // Licenses has just one valid version suffix.
      item[1].length === 1 &&
      item[0] !== null &&
      // APL will be considered Apache, rather than APL-1.0
      item[0] !== 'APL'
    )
  })
  .map(function createLastResorts (item) {
    return [item[0], item[1][0]]
  })

licensesWithVersions = undefined

// If all else fails, guess that strings containing certain substrings
// meant to identify certain licenses.
var lastResorts = [
  ['UNLI', 'Unlicense'],
  ['WTF', 'WTFPL'],
  ['2 CLAUSE', 'BSD-2-Clause'],
  ['2-CLAUSE', 'BSD-2-Clause'],
  ['3 CLAUSE', 'BSD-3-Clause'],
  ['3-CLAUSE', 'BSD-3-Clause'],
  ['AFFERO', 'AGPL-3.0-or-later'],
  ['AGPL', 'AGPL-3.0-or-later'],
  ['APACHE', 'Apache-2.0'],
  ['ARTISTIC', 'Artistic-2.0'],
  ['Affero', 'AGPL-3.0-or-later'],
  ['BEER', 'Beerware'],
  ['BOOST', 'BSL-1.0'],
  ['BSD', 'BSD-2-Clause'],
  ['CDDL', 'CDDL-1.1'],
  ['ECLIPSE', 'EPL-1.0'],
  ['FUCK', 'WTFPL'],
  ['GNU', 'GPL-3.0-or-later'],
  ['LGPL', 'LGPL-3.0-or-later'],
  ['GPLV1', 'GPL-1.0-only'],
  ['GPL-1', 'GPL-1.0-only'],
  ['GPLV2', 'GPL-2.0-only'],
  ['GPL-2', 'GPL-2.0-only'],
  ['GPL', 'GPL-3.0-or-later'],
  ['MIT +NO-FALSE-ATTRIBS', 'MITNFA'],
  ['MIT', 'MIT'],
  ['MPL', 'MPL-2.0'],
  ['X11', 'X11'],
  ['ZLIB', 'Zlib']
].concat(licensesWithOneVersion).sort(sortTranspositions)

var SUBSTRING = 0
var IDENTIFIER = 1

var validTransformation = function (identifier) {
  for (var i = 0; i < transforms.length; i++) {
    var transformed = transforms[i](identifier).trim()
    if (transformed !== identifier && valid(transformed)) {
      return transformed
    }
  }
  return null
}

var validLastResort = function (identifier) {
  var upperCased = identifier.toUpperCase()
  for (var i = 0; i < lastResorts.length; i++) {
    var lastResort = lastResorts[i]
    if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) {
      return lastResort[IDENTIFIER]
    }
  }
  return null
}

var anyCorrection = function (identifier, check) {
  for (var i = 0; i < transpositions.length; i++) {
    var transposition = transpositions[i]
    var transposed = transposition[TRANSPOSED]
    if (identifier.indexOf(transposed) > -1) {
      var corrected = identifier.replace(
        transposed,
        transposition[CORRECT]
      )
      var checked = check(corrected)
      if (checked !== null) {
        return checked
      }
    }
  }
  return null
}

module.exports = function (identifier, options) {
  options = options || {}
  var upgrade = options.upgrade === undefined ? true : !!options.upgrade
  function postprocess (value) {
    return upgrade ? upgradeGPLs(value) : value
  }
  var validArugment = (
    typeof identifier === 'string' &&
    identifier.trim().length !== 0
  )
  if (!validArugment) {
    throw Error('Invalid argument. Expected non-empty string.')
  }
  identifier = identifier.trim()
  if (valid(identifier)) {
    return postprocess(identifier)
  }
  var noPlus = identifier.replace(/\+$/, '').trim()
  if (valid(noPlus)) {
    return postprocess(noPlus)
  }
  var transformed = validTransformation(identifier)
  if (transformed !== null) {
    return postprocess(transformed)
  }
  transformed = anyCorrection(identifier, function (argument) {
    if (valid(argument)) {
      return argument
    }
    return validTransformation(argument)
  })
  if (transformed !== null) {
    return postprocess(transformed)
  }
  transformed = validLastResort(identifier)
  if (transformed !== null) {
    return postprocess(transformed)
  }
  transformed = anyCorrection(identifier, validLastResort)
  if (transformed !== null) {
    return postprocess(transformed)
  }
  return null
}

function upgradeGPLs (value) {
  if ([
    'GPL-1.0', 'LGPL-1.0', 'AGPL-1.0',
    'GPL-2.0', 'LGPL-2.0', 'AGPL-2.0',
    'LGPL-2.1'
  ].indexOf(value) !== -1) {
    return value + '-only'
  } else if ([
    'GPL-1.0+', 'GPL-2.0+', 'GPL-3.0+',
    'LGPL-2.0+', 'LGPL-2.1+', 'LGPL-3.0+',
    'AGPL-1.0+', 'AGPL-3.0+'
  ].indexOf(value) !== -1) {
    return value.replace(/\+$/, '-or-later')
  } else if (['GPL-3.0', 'LGPL-3.0', 'AGPL-3.0'].indexOf(value) !== -1) {
    return value + '-or-later'
  } else {
    return value
  }
}


spdx-correct

description: correct invalid SPDX expressions

keywords: SPDX law legal license metadata

lint: standard && standard-markdown README.md

test: defence README.md | replace-require-self | node && node test.js


code:
The package exports an array of strings. Each string is an identifier
for a license exception under the [Software Package Data Exchange
(SPDX)][SPDX] software license metadata standard.

[SPDX]: https://spdx.org

## Copyright and Licensing

### SPDX

"SPDX" is a federally registered United States trademark of The Linux
Foundation Corporation.

From version 2.0 of the [SPDX] specification:

> Copyright © 2010-2015 Linux Foundation and its Contributors. Licensed
> under the Creative Commons Attribution License 3.0 Unported. All other
> rights are expressly reserved.

The Linux Foundation and the SPDX working groups are good people. Only
they decide what "SPDX" means, as a standard and otherwise. I respect
their work and their rights. You should, too.

### This Package

> I created this package by copying exception identifiers out of the
> SPDX specification. That work was mechanical, routine, and required no
> creativity whatsoever. - Kyle Mitchell, package author

United States users concerned about intellectual property may wish to
discuss the following Supreme Court decisions with their attorneys:

- _Baker v. Selden_, 101 U.S. 99 (1879)

- _Feist Publications, Inc., v. Rural Telephone Service Co._,
  499 U.S. 340 (1991)


spdx-exceptions

description: list of SPDX standard license exceptions

author: The Linux Foundation

contributors: Kyle E. Mitchell <kyle@kemitchell.com> (https://kemitchell.com/)

build: node build.js

latest: node latest.js


code:
C. Scott Ananian <cscott@cscott.net> (http://cscott.net)
Kyle E. Mitchell <kyle@kemitchell.com> (https://kemitchell.com)
Shinnosuke Watanabe <snnskwtnb@gmail.com>
Antoine Motet <antoine.motet@gmail.com>


code:
This package parses [SPDX license expression](https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60) strings describing license terms, like [package.json license strings](https://docs.npmjs.com/files/package.json#license), into consistently structured ECMAScript objects.  The npm command-line interface depends on this package, as do many automatic license-audit tools.

In a nutshell:

```javascript
var parse = require('spdx-expression-parse')
var assert = require('assert')

assert.deepEqual(
  // Licensed under the terms of the Two-Clause BSD License.
  parse('BSD-2-Clause'),
  {license: 'BSD-2-Clause'}
)

assert.throws(function () {
  // An invalid SPDX license expression.
  // Should be `Apache-2.0`.
  parse('Apache 2')
})

assert.deepEqual(
  // Dual licensed under either:
  // - LGPL 2.1
  // - a combination of Three-Clause BSD and MIT
  parse('(LGPL-2.1 OR BSD-3-Clause AND MIT)'),
  {
    left: {license: 'LGPL-2.1'},
    conjunction: 'or',
    right: {
      left: {license: 'BSD-3-Clause'},
      conjunction: 'and',
      right: {license: 'MIT'}
    }
  }
)
```

The syntax comes from the [Software Package Data eXchange (SPDX)](https://spdx.org/), a standard from the [Linux Foundation](https://www.linuxfoundation.org) for shareable data about software package license terms.  SPDX aims to make sharing and auditing license data easy, especially for users of open-source software.

The bulk of the SPDX standard describes syntax and semantics of XML metadata files.  This package implements two lightweight, plain-text components of that larger standard:

1.  The [license list](https://spdx.org/licenses), a mapping from specific string identifiers, like `Apache-2.0`, to standard form license texts and bolt-on license exceptions.  The [spdx-license-ids](https://www.npmjs.com/package/spdx-license-ids) and [spdx-exceptions](https://www.npmjs.com/package/spdx-exceptions) packages implement the license list.  `spdx-expression-parse` depends on and `require()`s them.

    Any license identifier from the license list is a valid license expression:

    ```javascript
    var identifiers = []
      .concat(require('spdx-license-ids'))
      .concat(require('spdx-license-ids/deprecated'))

    identifiers.forEach(function (id) {
      assert.deepEqual(parse(id), {license: id})
    })
    ```

    So is any license identifier `WITH` a standardized license exception:

    ```javascript
    identifiers.forEach(function (id) {
      require('spdx-exceptions').forEach(function (e) {
        assert.deepEqual(
          parse(id + ' WITH ' + e),
          {license: id, exception: e}
        )
      })
    })
    ```

2.  The license expression language, for describing simple and complex license terms, like `MIT` for MIT-licensed and `(GPL-2.0 OR Apache-2.0)` for dual-licensing under GPL 2.0 and Apache 2.0.  `spdx-expression-parse` itself implements license expression language, exporting a parser.

    ```javascript
    assert.deepEqual(
      // Licensed under a combination of:
      // - the MIT License AND
      // - a combination of:
      //   - LGPL 2.1 (or a later version) AND
      //   - Three-Clause BSD
      parse('(MIT AND (LGPL-2.1+ AND BSD-3-Clause))'),
      {
        left: {license: 'MIT'},
        conjunction: 'and',
        right: {
          left: {license: 'LGPL-2.1', plus: true},
          conjunction: 'and',
          right: {license: 'BSD-3-Clause'}
        }
      }
    )
    ```

The Linux Foundation and its contributors license the SPDX standard under the terms of [the Creative Commons Attribution License 3.0 Unported (SPDX: "CC-BY-3.0")](http://spdx.org/licenses/CC-BY-3.0).  "SPDX" is a United States federally registered trademark of the Linux Foundation.  The authors of this package license their work under the terms of the MIT License.


code:
'use strict'

var scan = require('./scan')
var parse = require('./parse')

module.exports = function (source) {
  return parse(scan(source))
}


spdx-expression-parse

description: parse SPDX license expressions

author: Kyle E. Mitchell <kyle@kemitchell.com> (https://kemitchell.com)

keywords: SPDX law legal license metadata package package.json standards

license: MIT

lint: standard

test:readme: defence -i javascript README.md | replace-require-self | node

test:suite: node test.js

test: npm run test:suite && npm run test:readme


code:
'use strict'

// The ABNF grammar in the spec is totally ambiguous.
//
// This parser follows the operator precedence defined in the
// `Order of Precedence and Parentheses` section.

module.exports = function (tokens) {
  var index = 0

  function hasMore () {
    return index < tokens.length
  }

  function token () {
    return hasMore() ? tokens[index] : null
  }

  function next () {
    if (!hasMore()) {
      throw new Error()
    }
    index++
  }

  function parseOperator (operator) {
    var t = token()
    if (t && t.type === 'OPERATOR' && operator === t.string) {
      next()
      return t.string
    }
  }

  function parseWith () {
    if (parseOperator('WITH')) {
      var t = token()
      if (t && t.type === 'EXCEPTION') {
        next()
        return t.string
      }
      throw new Error('Expected exception after `WITH`')
    }
  }

  function parseLicenseRef () {
    // TODO: Actually, everything is concatenated into one string
    // for backward-compatibility but it could be better to return
    // a nice structure.
    var begin = index
    var string = ''
    var t = token()
    if (t.type === 'DOCUMENTREF') {
      next()
      string += 'DocumentRef-' + t.string + ':'
      if (!parseOperator(':')) {
        throw new Error('Expected `:` after `DocumentRef-...`')
      }
    }
    t = token()
    if (t.type === 'LICENSEREF') {
      next()
      string += 'LicenseRef-' + t.string
      return { license: string }
    }
    index = begin
  }

  function parseLicense () {
    var t = token()
    if (t && t.type === 'LICENSE') {
      next()
      var node = { license: t.string }
      if (parseOperator('+')) {
        node.plus = true
      }
      var exception = parseWith()
      if (exception) {
        node.exception = exception
      }
      return node
    }
  }

  function parseParenthesizedExpression () {
    var left = parseOperator('(')
    if (!left) {
      return
    }

    var expr = parseExpression()

    if (!parseOperator(')')) {
      throw new Error('Expected `)`')
    }

    return expr
  }

  function parseAtom () {
    return (
      parseParenthesizedExpression() ||
      parseLicenseRef() ||
      parseLicense()
    )
  }

  function makeBinaryOpParser (operator, nextParser) {
    return function parseBinaryOp () {
      var left = nextParser()
      if (!left) {
        return
      }

      if (!parseOperator(operator)) {
        return left
      }

      var right = parseBinaryOp()
      if (!right) {
        throw new Error('Expected expression')
      }
      return {
        left: left,
        conjunction: operator.toLowerCase(),
        right: right
      }
    }
  }

  var parseAnd = makeBinaryOpParser('AND', parseAtom)
  var parseExpression = makeBinaryOpParser('OR', parseAnd)

  var node = parseExpression()
  if (!node || hasMore()) {
    throw new Error('Syntax error')
  }
  return node
}


code:
'use strict'

var licenses = []
  .concat(require('spdx-license-ids'))
  .concat(require('spdx-license-ids/deprecated'))
var exceptions = require('spdx-exceptions')

module.exports = function (source) {
  var index = 0

  function hasMore () {
    return index < source.length
  }

  // `value` can be a regexp or a string.
  // If it is recognized, the matching source string is returned and
  // the index is incremented. Otherwise `undefined` is returned.
  function read (value) {
    if (value instanceof RegExp) {
      var chars = source.slice(index)
      var match = chars.match(value)
      if (match) {
        index += match[0].length
        return match[0]
      }
    } else {
      if (source.indexOf(value, index) === index) {
        index += value.length
        return value
      }
    }
  }

  function skipWhitespace () {
    read(/[ ]*/)
  }

  function operator () {
    var string
    var possibilities = ['WITH', 'AND', 'OR', '(', ')', ':', '+']
    for (var i = 0; i < possibilities.length; i++) {
      string = read(possibilities[i])
      if (string) {
        break
      }
    }

    if (string === '+' && index > 1 && source[index - 2] === ' ') {
      throw new Error('Space before `+`')
    }

    return string && {
      type: 'OPERATOR',
      string: string
    }
  }

  function idstring () {
    return read(/[A-Za-z0-9-.]+/)
  }

  function expectIdstring () {
    var string = idstring()
    if (!string) {
      throw new Error('Expected idstring at offset ' + index)
    }
    return string
  }

  function documentRef () {
    if (read('DocumentRef-')) {
      var string = expectIdstring()
      return { type: 'DOCUMENTREF', string: string }
    }
  }

  function licenseRef () {
    if (read('LicenseRef-')) {
      var string = expectIdstring()
      return { type: 'LICENSEREF', string: string }
    }
  }

  function identifier () {
    var begin = index
    var string = idstring()

    if (licenses.indexOf(string) !== -1) {
      return {
        type: 'LICENSE',
        string: string
      }
    } else if (exceptions.indexOf(string) !== -1) {
      return {
        type: 'EXCEPTION',
        string: string
      }
    }

    index = begin
  }

  // Tries to read the next token. Returns `undefined` if no token is
  // recognized.
  function parseToken () {
    // Ordering matters
    return (
      operator() ||
      documentRef() ||
      licenseRef() ||
      identifier()
    )
  }

  var tokens = []
  while (hasMore()) {
    skipWhitespace()
    if (!hasMore()) {
      break
    }

    var token = parseToken()
    if (!token) {
      throw new Error('Unexpected `' + source[index] +
                      '` at offset ' + index)
    }

    tokens.push(token)
  }
  return tokens
}


code:
# spdx-license-ids

[![npm version](https://img.shields.io/npm/v/spdx-license-ids.svg)](https://www.npmjs.com/package/spdx-license-ids)
[![Github Actions](https://action-badges.now.sh/jslicense/spdx-license-ids)](https://wdp9fww0r9.execute-api.us-west-2.amazonaws.com/production/results/jslicense/spdx-license-ids)

A list of [SPDX license](https://spdx.org/licenses/) identifiers

## Installation

[Download JSON directly](https://raw.githubusercontent.com/jslicense/spdx-license-ids/main/index.json), or [use](https://docs.npmjs.com/cli/install) [npm](https://docs.npmjs.com/about-npm/):

```
npm install spdx-license-ids
```

## [Node.js](https://nodejs.org/) API

### require('spdx-license-ids')

Type: `string[]`

All license IDs except for the currently deprecated ones.

```javascript
const ids = require('spdx-license-ids');
//=> ['0BSD', 'AAL', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'AGPL-1.0-only', ...]

ids.includes('BSD-3-Clause'); //=> true
ids.includes('CC-BY-1.0'); //=> true

ids.includes('GPL-3.0'); //=> false
```

### require('spdx-license-ids/deprecated')

Type: `string[]`

Deprecated license IDs.

```javascript
const deprecatedIds = require('spdx-license-ids/deprecated');
//=> ['AGPL-1.0', 'AGPL-3.0', 'GFDL-1.1', 'GFDL-1.2', 'GFDL-1.3', 'GPL-1.0', 'GPL-2.0', ...]

deprecatedIds.includes('BSD-3-Clause'); //=> false
deprecatedIds.includes('CC-BY-1.0'); //=> false

deprecatedIds.includes('GPL-3.0'); //=> true
```

## License

[Creative Commons Zero v1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/deed)


spdx-license-ids

description: A list of SPDX license identifiers

repository: jslicense/spdx-license-ids

pretest: eslint .

test: node test.js

keywords: spdx license licenses id identifier identifiers json array oss


code:
sudo: false

language: node_js

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

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


code:
# spdy-transport

[![Build Status](https://travis-ci.org/spdy-http2/spdy-transport.svg?branch=master)](http://travis-ci.org/spdy-http2/spdy-transport)
[![NPM version](https://badge.fury.io/js/spdy-transport.svg)](http://badge.fury.io/js/spdy-transport)
[![dependencies Status](https://david-dm.org/spdy-http2/spdy-transport/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/spdy-transport)
[![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)

> SPDY/HTTP2 generic transport implementation.

## Usage

```javascript
var transport = require('spdy-transport');

// NOTE: socket is some stream or net.Socket instance, may be an argument
// of `net.createServer`'s connection handler.

var server = transport.connection.create(socket, {
  protocol: 'http2',
  isServer: true
});

server.on('stream', function(stream) {
  console.log(stream.method, stream.path, stream.headers);
  stream.respond(200, {
    header: 'value'
  });

  stream.on('readable', function() {
    var chunk = stream.read();
    if (!chunk)
      return;

    console.log(chunk);
  });

  stream.on('end', function() {
    console.log('end');
  });

  // And other node.js Stream APIs
  // ...
});
```

## 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]: http://json.org/
[1]: http://github.com/indutny/bud-backend
[2]: https://github.com/nodejs/io.js
[3]: https://github.com/libuv/libuv
[4]: http://openssl.org/


code:
'use strict'

var transport = exports

// Exports utils
transport.utils = require('./spdy-transport/utils')

// Export parser&framer
transport.protocol = {}
transport.protocol.base = require('./spdy-transport/protocol/base')
transport.protocol.spdy = require('./spdy-transport/protocol/spdy')
transport.protocol.http2 = require('./spdy-transport/protocol/http2')

// Window
transport.Window = require('./spdy-transport/window')

// Priority Tree
transport.Priority = require('./spdy-transport/priority')

// Export Connection and Stream
transport.Stream = require('./spdy-transport/stream').Stream
transport.Connection = require('./spdy-transport/connection').Connection

// Just for `transport.connection.create()`
transport.connection = transport.Connection


code:
'use strict'

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

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

var Stream = transport.Stream

function Connection (socket, options) {
  EventEmitter.call(this)

  var state = {}
  this._spdyState = state

  // NOTE: There's a big trick here. Connection is used as a `this` argument
  // to the wrapped `connection` event listener.
  // socket end doesn't necessarly mean connection drop
  this.httpAllowHalfOpen = true

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

  // Protocol info
  state.protocol = transport.protocol[options.protocol]
  state.version = null
  state.constants = state.protocol.constants
  state.pair = null
  state.isServer = options.isServer

  // Root of priority tree (i.e. stream id = 0)
  state.priorityRoot = new transport.Priority({
    defaultWeight: state.constants.DEFAULT_WEIGHT,
    maxCount: transport.protocol.base.constants.MAX_PRIORITY_STREAMS
  })

  // Defaults
  state.maxStreams = options.maxStreams ||
                     state.constants.MAX_CONCURRENT_STREAMS

  state.autoSpdy31 = options.protocol.name !== 'h2' && options.autoSpdy31
  state.acceptPush = options.acceptPush === undefined
    ? !state.isServer
    : options.acceptPush

  if (options.maxChunk === false) { state.maxChunk = Infinity } else if (options.maxChunk === undefined) { state.maxChunk = transport.protocol.base.constants.DEFAULT_MAX_CHUNK } else {
    state.maxChunk = options.maxChunk
  }

  // Connection-level flow control
  var windowSize = options.windowSize || 1 << 20
  state.window = new transport.Window({
    id: 0,
    isServer: state.isServer,
    recv: {
      size: state.constants.DEFAULT_WINDOW,
      max: state.constants.MAX_INITIAL_WINDOW_SIZE
    },
    send: {
      size: state.constants.DEFAULT_WINDOW,
      max: state.constants.MAX_INITIAL_WINDOW_SIZE
    }
  })

  // It starts with DEFAULT_WINDOW, update must be sent to change it on client
  state.window.recv.setMax(windowSize)

  // Boilerplate for Stream constructor
  state.streamWindow = new transport.Window({
    id: -1,
    isServer: state.isServer,
    recv: {
      size: windowSize,
      max: state.constants.MAX_INITIAL_WINDOW_SIZE
    },
    send: {
      size: state.constants.DEFAULT_WINDOW,
      max: state.constants.MAX_INITIAL_WINDOW_SIZE
    }
  })

  // Various state info
  state.pool = state.protocol.compressionPool.create(options.headerCompression)
  state.counters = {
    push: 0,
    stream: 0
  }

  // Init streams list
  state.stream = {
    map: {},
    count: 0,
    nextId: state.isServer ? 2 : 1,
    lastId: {
      both: 0,
      received: 0
    }
  }
  state.ping = {
    nextId: state.isServer ? 2 : 1,
    map: {}
  }
  state.goaway = false

  // Debug
  state.debug = state.isServer ? debug.server : debug.client

  // X-Forwarded feature
  state.xForward = null

  // Create parser and hole for framer
  state.parser = state.protocol.parser.create({
    // NOTE: needed to distinguish ping from ping ACK in SPDY
    isServer: state.isServer,
    window: state.window
  })
  state.framer = state.protocol.framer.create({
    window: state.window,
    timeout: state.timeout
  })

  // SPDY has PUSH enabled on servers
  if (state.protocol.name === 'spdy') {
    state.framer.enablePush(state.isServer)
  }

  if (!state.isServer) { state.parser.skipPreface() }

  this.socket = socket

  this._init()
}
util.inherits(Connection, EventEmitter)
exports.Connection = Connection

Connection.create = function create (socket, options) {
  return new Connection(socket, options)
}

Connection.prototype._init = function init () {
  var self = this
  var state = this._spdyState
  var pool = state.pool

  // Initialize session window
  state.window.recv.on('drain', function () {
    self._onSessionWindowDrain()
  })

  // Initialize parser
  state.parser.on('data', function (frame) {
    self._handleFrame(frame)
  })
  state.parser.once('version', function (version) {
    self._onVersion(version)
  })

  // Propagate parser errors
  state.parser.on('error', function (err) {
    self._onParserError(err)
  })

  // Propagate framer errors
  state.framer.on('error', function (err) {
    self.emit('error', err)
  })

  this.socket.pipe(state.parser)
  state.framer.pipe(this.socket)

  // Allow high-level api to catch socket errors
  this.socket.on('error', function onSocketError (e) {
    self.emit('error', e)
  })

  this.socket.once('close', function onclose (hadError) {
    var err
    if (hadError) {
      err = new Error('socket hang up')
      err.code = 'ECONNRESET'
    }

    self.destroyStreams(err)
    self.emit('close')

    if (state.pair) {
      pool.put(state.pair)
    }

    state.framer.resume()
  })

  // Reset timeout on close
  this.once('close', function () {
    self.setTimeout(0)
  })

  function _onWindowOverflow () {
    self._onWindowOverflow()
  }

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

  // Do not allow half-open connections
  this.socket.allowHalfOpen = false
}

Connection.prototype._onVersion = function _onVersion (version) {
  var state = this._spdyState
  var prev = state.version
  var parser = state.parser
  var framer = state.framer
  var pool = state.pool

  state.version = version
  state.debug('id=0 version=%d', version)

  // Ignore transition to 3.1
  if (!prev) {
    state.pair = pool.get(version)
    parser.setCompression(state.pair)
    framer.setCompression(state.pair)
  }
  framer.setVersion(version)

  if (!state.isServer) {
    framer.prefaceFrame()
    if (state.xForward !== null) {
      framer.xForwardedFor({ host: state.xForward })
    }
  }

  // Send preface+settings frame (once)
  framer.settingsFrame({
    max_header_list_size: state.constants.DEFAULT_MAX_HEADER_LIST_SIZE,
    max_concurrent_streams: state.maxStreams,
    enable_push: state.acceptPush ? 1 : 0,
    initial_window_size: state.window.recv.max
  })

  // Update session window
  if (state.version >= 3.1 || (state.isServer && state.autoSpdy31)) { this._onSessionWindowDrain() }

  this.emit('version', version)
}

Connection.prototype._onParserError = function _onParserError (err) {
  var state = this._spdyState

  // Prevent further errors
  state.parser.kill()

  // Send GOAWAY
  if (err instanceof transport.protocol.base.utils.ProtocolError) {
    this._goaway({
      lastId: state.stream.lastId.both,
      code: err.code,
      extra: err.message,
      send: true
    })
  }

  this.emit('error', err)
}

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

  state.debug('id=0 frame', frame)
  state.timeout.reset()

  // For testing purposes
  this.emit('frame', frame)

  var stream

  // Session window update
  if (frame.type === 'WINDOW_UPDATE' && frame.id === 0) {
    if (state.version < 3.1 && state.autoSpdy31) {
      state.debug('id=0 switch version to 3.1')
      state.version = 3.1
      this.emit('version', 3.1)
    }
    state.window.send.update(frame.delta)
    return
  }

  if (state.isServer && frame.type === 'PUSH_PROMISE') {
    state.debug('id=0 server PUSH_PROMISE')
    this._goaway({
      lastId: state.stream.lastId.both,
      code: 'PROTOCOL_ERROR',
      send: true
    })
    return
  }

  if (!stream && frame.id !== undefined) {
    // Load created one
    stream = state.stream.map[frame.id]

    // Fail if not found
    if (!stream &&
        frame.type !== 'HEADERS' &&
        frame.type !== 'PRIORITY' &&
        frame.type !== 'RST') {
      // Other side should destroy the stream upon receiving GOAWAY
      if (this._isGoaway(frame.id)) { return }

      state.debug('id=0 stream=%d not found', frame.id)
      state.framer.rstFrame({ id: frame.id, code: 'INVALID_STREAM' })
      return
    }
  }

  // Create new stream
  if (!stream && frame.type === 'HEADERS') {
    this._handleHeaders(frame)
    return
  }

  if (stream) {
    stream._handleFrame(frame)
  } else if (frame.type === 'SETTINGS') {
    this._handleSettings(frame.settings)
  } else if (frame.type === 'ACK_SETTINGS') {
    // TODO(indutny): handle it one day
  } else if (frame.type === 'PING') {
    this._handlePing(frame)
  } else if (frame.type === 'GOAWAY') {
    this._handleGoaway(frame)
  } else if (frame.type === 'X_FORWARDED_FOR') {
    // Set X-Forwarded-For only once
    if (state.xForward === null) {
      state.xForward = frame.host
    }
  } else if (frame.type === 'PRIORITY') {
    // TODO(indutny): handle this
  } else {
    state.debug('id=0 unknown frame type: %s', frame.type)
  }
}

Connection.prototype._onWindowOverflow = function _onWindowOverflow () {
  var state = this._spdyState
  state.debug('id=0 window overflow')
  this._goaway({
    lastId: state.stream.lastId.both,
    code: 'FLOW_CONTROL_ERROR',
    send: true
  })
}

Connection.prototype._isGoaway = function _isGoaway (id) {
  var state = this._spdyState
  if (state.goaway !== false && state.goaway < id) { return true }
  return false
}

Connection.prototype._getId = function _getId () {
  var state = this._spdyState

  var id = state.stream.nextId
  state.stream.nextId += 2
  return id
}

Connection.prototype._createStream = function _createStream (uri) {
  var state = this._spdyState
  var id = uri.id
  if (id === undefined) { id = this._getId() }

  var isGoaway = this._isGoaway(id)

  if (uri.push && !state.acceptPush) {
    state.debug('id=0 push disabled promisedId=%d', id)

    // Fatal error
    this._goaway({
      lastId: state.stream.lastId.both,
      code: 'PROTOCOL_ERROR',
      send: true
    })
    isGoaway = true
  }

  var stream = new Stream(this, {
    id: id,
    request: uri.request !== false,
    method: uri.method,
    path: uri.path,
    host: uri.host,
    priority: uri.priority,
    headers: uri.headers,
    parent: uri.parent,
    readable: !isGoaway && uri.readable,
    writable: !isGoaway && uri.writable
  })
  var self = this

  // Just an empty stream for API consistency
  if (isGoaway) {
    return stream
  }

  state.stream.lastId.both = Math.max(state.stream.lastId.both, id)

  state.debug('id=0 add stream=%d', stream.id)
  state.stream.map[stream.id] = stream
  state.stream.count++
  state.counters.stream++
  if (stream.parent !== null) {
    state.counters.push++
  }

  stream.once('close', function () {
    self._removeStream(stream)
  })

  return stream
}

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

  // Must be HEADERS frame after stream close
  if (frame.id <= state.stream.lastId.received) { return }

  // Someone is using our ids!
  if ((frame.id + state.stream.nextId) % 2 === 0) {
    state.framer.rstFrame({ id: frame.id, code: 'PROTOCOL_ERROR' })
    return
  }

  var stream = this._createStream({
    id: frame.id,
    request: false,
    method: frame.headers[':method'],
    path: frame.headers[':path'],
    host: frame.headers[':authority'],
    priority: frame.priority,
    headers: frame.headers,
    writable: frame.writable
  })

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

  state.stream.lastId.received = Math.max(
    state.stream.lastId.received,
    stream.id
  )

  // TODO(indutny) handle stream limit
  if (!this.emit('stream', stream)) {
    // No listeners was set - abort the stream
    stream.abort()
    return
  }

  // Create fake frame to simulate end of the data
  if (frame.fin) {
    stream._handleFrame({ type: 'FIN', fin: true })
  }

  return stream
}

Connection.prototype._onSessionWindowDrain = function _onSessionWindowDrain () {
  var state = this._spdyState
  if (state.version < 3.1 && !(state.isServer && state.autoSpdy31)) {
    return
  }

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

  state.debug('id=0 session window drain, update by %d', delta)

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

Connection.prototype.start = function start (version) {
  this._spdyState.parser.setVersion(version)
}

// Mostly for testing
Connection.prototype.getVersion = function getVersion () {
  return this._spdyState.version
}

Connection.prototype._handleSettings = function _handleSettings (settings) {
  var state = this._spdyState

  state.framer.ackSettingsFrame()

  this._setDefaultWindow(settings)
  if (settings.max_frame_size) { state.framer.setMaxFrameSize(settings.max_frame_size) }

  // TODO(indutny): handle max_header_list_size
  if (settings.header_table_size) {
    try {
      state.pair.compress.updateTableSize(settings.header_table_size)
    } catch (e) {
      this._goaway({
        lastId: 0,
        code: 'PROTOCOL_ERROR',
        send: true
      })
      return
    }
  }

  // HTTP2 clients needs to enable PUSH streams explicitly
  if (state.protocol.name !== 'spdy') {
    if (settings.enable_push === undefined) {
      state.framer.enablePush(state.isServer)
    } else {
      state.framer.enablePush(settings.enable_push === 1)
    }
  }

  // TODO(indutny): handle max_concurrent_streams
}

Connection.prototype._setDefaultWindow = function _setDefaultWindow (settings) {
  if (settings.initial_window_size === undefined) {
    return
  }

  var state = this._spdyState

  // Update defaults
  var window = state.streamWindow
  window.send.setMax(settings.initial_window_size)

  // Update existing streams
  Object.keys(state.stream.map).forEach(function (id) {
    var stream = state.stream.map[id]
    var window = stream._spdyState.window

    window.send.updateMax(settings.initial_window_size)
  })
}

Connection.prototype._handlePing = function handlePing (frame) {
  var self = this
  var state = this._spdyState

  // Handle incoming PING
  if (!frame.ack) {
    state.framer.pingFrame({
      opaque: frame.opaque,
      ack: true
    })

    self.emit('ping', frame.opaque)
    return
  }

  // Handle reply PING
  var hex = frame.opaque.toString('hex')
  if (!state.ping.map[hex]) {
    return
  }
  var ping = state.ping.map[hex]
  delete state.ping.map[hex]

  if (ping.cb) {
    ping.cb(null)
  }
}

Connection.prototype._handleGoaway = function handleGoaway (frame) {
  this._goaway({
    lastId: frame.lastId,
    code: frame.code,
    send: false
  })
}

Connection.prototype.ping = function ping (callback) {
  var state = this._spdyState

  // HTTP2 is using 8-byte opaque
  var opaque = Buffer.alloc(state.constants.PING_OPAQUE_SIZE)
  opaque.fill(0)
  opaque.writeUInt32BE(state.ping.nextId, opaque.length - 4)
  state.ping.nextId += 2

  state.ping.map[opaque.toString('hex')] = { cb: callback }
  state.framer.pingFrame({
    opaque: opaque,
    ack: false
  })
}

Connection.prototype.getCounter = function getCounter (name) {
  return this._spdyState.counters[name]
}

Connection.prototype.reserveStream = function reserveStream (uri, callback) {
  var stream = this._createStream(uri)

  // GOAWAY
  if (this._isGoaway(stream.id)) {
    var err = new Error('Can\'t send request after GOAWAY')
    process.nextTick(function () {
      if (callback) { callback(err) } else {
        stream.emit('error', err)
      }
    })
    return stream
  }

  if (callback) {
    process.nextTick(function () {
      callback(null, stream)
    })
  }

  return stream
}

Connection.prototype.request = function request (uri, callback) {
  var stream = this.reserveStream(uri, function (err) {
    if (err) {
      if (callback) {
        callback(err)
      } else {
        stream.emit('error', err)
      }
      return
    }

    if (stream._wasSent()) {
      if (callback) {
        callback(null, stream)
      }
      return
    }

    stream.send(function (err) {
      if (err) {
        if (callback) { return callback(err) } else { return stream.emit('error', err) }
      }

      if (callback) {
        callback(null, stream)
      }
    })
  })

  return stream
}

Connection.prototype._removeStream = function _removeStream (stream) {
  var state = this._spdyState

  state.debug('id=0 remove stream=%d', stream.id)
  delete state.stream.map[stream.id]
  state.stream.count--

  if (state.stream.count === 0) {
    this.emit('_streamDrain')
  }
}

Connection.prototype._goaway = function _goaway (params) {
  var state = this._spdyState
  var self = this

  state.goaway = params.lastId
  state.debug('id=0 goaway from=%d', state.goaway)

  Object.keys(state.stream.map).forEach(function (id) {
    var stream = state.stream.map[id]

    // Abort every stream started after GOAWAY
    if (stream.id <= params.lastId) {
      return
    }

    stream.abort()
    stream.emit('error', new Error('New stream after GOAWAY'))
  })

  function finish () {
    // Destroy socket if there are no streams
    if (state.stream.count === 0 || params.code !== 'OK') {
      // No further frames should be processed
      state.parser.kill()

      process.nextTick(function () {
        var err = new Error('Fatal error: ' + params.code)
        self._onStreamDrain(err)
      })
      return
    }

    self.on('_streamDrain', self._onStreamDrain)
  }

  if (params.send) {
    // Make sure that GOAWAY frame is sent before dumping framer
    state.framer.goawayFrame({
      lastId: params.lastId,
      code: params.code,
      extra: params.extra
    }, finish)
  } else {
    finish()
  }
}

Connection.prototype._onStreamDrain = function _onStreamDrain (error) {
  var state = this._spdyState

  state.debug('id=0 _onStreamDrain')

  state.framer.dump()
  state.framer.unpipe(this.socket)
  state.framer.resume()

  if (this.socket.destroySoon) {
    this.socket.destroySoon()
  }
  this.emit('close', error)
}

Connection.prototype.end = function end (callback) {
  var state = this._spdyState

  if (callback) {
    this.once('close', callback)
  }
  this._goaway({
    lastId: state.stream.lastId.both,
    code: 'OK',
    send: true
  })
}

Connection.prototype.destroyStreams = function destroyStreams (err) {
  var state = this._spdyState
  Object.keys(state.stream.map).forEach(function (id) {
    var stream = state.stream.map[id]

    stream.destroy()
    if (err) {
      stream.emit('error', err)
    }
  })
}

Connection.prototype.isServer = function isServer () {
  return this._spdyState.isServer
}

Connection.prototype.getXForwardedFor = function getXForwardFor () {
  return this._spdyState.xForward
}

Connection.prototype.sendXForwardedFor = function sendXForwardedFor (host) {
  var state = this._spdyState
  if (state.version !== null) {
    state.framer.xForwardedFor({ host: host })
  } else {
    state.xForward = host
  }
}

Connection.prototype.pushPromise = function pushPromise (parent, uri, callback) {
  var state = this._spdyState

  var stream = this._createStream({
    request: false,
    parent: parent,
    method: uri.method,
    path: uri.path,
    host: uri.host,
    priority: uri.priority,
    headers: uri.headers,
    readable: false
  })

  var err

  // TODO(indutny): deduplicate this logic somehow
  if (this._isGoaway(stream.id)) {
    err = new Error('Can\'t send PUSH_PROMISE after GOAWAY')

    process.nextTick(function () {
      if (callback) {
        callback(err)
      } else {
        stream.emit('error', err)
      }
    })
    return stream
  }

  if (uri.push && !state.acceptPush) {
    err = new Error(
      'Can\'t send PUSH_PROMISE, other side won\'t accept it')
    process.nextTick(function () {
      if (callback) { callback(err) } else {
        stream.emit('error', err)
      }
    })
    return stream
  }

  stream._sendPush(uri.status, uri.response, function (err) {
    if (!callback) {
      if (err) {
        stream.emit('error', err)
      }
      return
    }

    if (err) { return callback(err) }
    callback(null, stream)
  })

  return stream
}

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

  state.timeout.set(delay, callback)
}


code:
'use strict'

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

var assert = require('assert')
var debug = require('debug')('spdy:priority')

function PriorityNode (tree, options) {
  this.tree = tree

  this.id = options.id
  this.parent = options.parent
  this.weight = options.weight

  // To be calculated in `addChild`
  this.priorityFrom = 0
  this.priorityTo = 1
  this.priority = 1

  this.children = {
    list: [],
    weight: 0
  }

  if (this.parent !== null) {
    this.parent.addChild(this)
  }
}

function compareChildren (a, b) {
  return a.weight === b.weight ? a.id - b.id : a.weight - b.weight
}

PriorityNode.prototype.toJSON = function toJSON () {
  return {
    parent: this.parent,
    weight: this.weight,
    exclusive: this.exclusive
  }
}

PriorityNode.prototype.getPriority = function getPriority () {
  return this.priority
}

PriorityNode.prototype.getPriorityRange = function getPriorityRange () {
  return { from: this.priorityFrom, to: this.priorityTo }
}

PriorityNode.prototype.addChild = function addChild (child) {
  child.parent = this
  utils.binaryInsert(this.children.list, child, compareChildren)
  this.children.weight += child.weight

  this._updatePriority(this.priorityFrom, this.priorityTo)
}

PriorityNode.prototype.remove = function remove () {
  assert(this.parent, 'Can\'t remove root node')

  this.parent.removeChild(this)
  this.tree._removeNode(this)

  // Move all children to the parent
  for (var i = 0; i < this.children.list.length; i++) {
    this.parent.addChild(this.children.list[i])
  }
}

PriorityNode.prototype.removeChild = function removeChild (child) {
  this.children.weight -= child.weight
  var index = utils.binarySearch(this.children.list, child, compareChildren)
  if (index !== -1 && this.children.list.length >= index) {
    this.children.list.splice(index, 1)
  }
}

PriorityNode.prototype.removeChildren = function removeChildren () {
  var children = this.children.list
  this.children.list = []
  this.children.weight = 0
  return children
}

PriorityNode.prototype._updatePriority = function _updatePriority (from, to) {
  this.priority = to - from
  this.priorityFrom = from
  this.priorityTo = to

  var weight = 0
  for (var i = 0; i < this.children.list.length; i++) {
    var node = this.children.list[i]
    var nextWeight = weight + node.weight

    node._updatePriority(
      from + this.priority * (weight / this.children.weight),
      from + this.priority * (nextWeight / this.children.weight)
    )
    weight = nextWeight
  }
}

function PriorityTree (options) {
  this.map = {}
  this.list = []
  this.defaultWeight = options.defaultWeight || 16

  this.count = 0
  this.maxCount = options.maxCount

  // Root
  this.root = this.add({
    id: 0,
    parent: null,
    weight: 1
  })
}
module.exports = PriorityTree

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

PriorityTree.prototype.add = function add (options) {
  if (options.id === options.parent) {
    return this.addDefault(options.id)
  }

  var parent = options.parent === null ? null : this.map[options.parent]
  if (parent === undefined) {
    return this.addDefault(options.id)
  }

  debug('add node=%d parent=%d weight=%d exclusive=%d',
    options.id,
    options.parent === null ? -1 : options.parent,
    options.weight || this.defaultWeight,
    options.exclusive ? 1 : 0)

  var children
  if (options.exclusive) {
    children = parent.removeChildren()
  }

  var node = new PriorityNode(this, {
    id: options.id,
    parent: parent,
    weight: options.weight || this.defaultWeight
  })
  this.map[options.id] = node

  if (options.exclusive) {
    for (var i = 0; i < children.length; i++) {
      node.addChild(children[i])
    }
  }

  this.count++
  if (this.count > this.maxCount) {
    debug('hit maximum remove id=%d', this.list[0].id)
    this.list.shift().remove()
  }

  // Root node is not subject to removal
  if (node.parent !== null) {
    this.list.push(node)
  }

  return node
}

// Only for testing, should use `node`'s methods
PriorityTree.prototype.get = function get (id) {
  return this.map[id]
}

PriorityTree.prototype.addDefault = function addDefault (id) {
  debug('creating default node')
  return this.add({ id: id, parent: 0, weight: this.defaultWeight })
}

PriorityTree.prototype._removeNode = function _removeNode (node) {
  delete this.map[node.id]
  var index = utils.binarySearch(this.list, node, compareChildren)
  this.list.splice(index, 1)
  this.count--
}


code:
exports.DEFAULT_METHOD = 'GET'
exports.DEFAULT_HOST = 'localhost'
exports.MAX_PRIORITY_STREAMS = 100
exports.DEFAULT_MAX_CHUNK = 8 * 1024


code:
'use strict'

var util = require('util')

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

function Framer (options) {
  Scheduler.call(this)

  this.version = null
  this.compress = null
  this.window = options.window
  this.timeout = options.timeout

  // Wait for `enablePush`
  this.pushEnabled = null
}
util.inherits(Framer, Scheduler)
module.exports = Framer

Framer.prototype.setVersion = function setVersion (version) {
  this.version = version
  this.emit('version')
}

Framer.prototype.setCompression = function setCompresion (pair) {
  this.compress = new transport.utils.LockStream(pair.compress)
}

Framer.prototype.enablePush = function enablePush (enable) {
  this.pushEnabled = enable
  this.emit('_pushEnabled')
}

Framer.prototype._checkPush = function _checkPush (callback) {
  if (this.pushEnabled === null) {
    this.once('_pushEnabled', function () {
      this._checkPush(callback)
    })
    return
  }

  var err = null
  if (!this.pushEnabled) {
    err = new Error('PUSH_PROMISE disabled by other side')
  }
  process.nextTick(function () {
    return callback(err)
  })
}

Framer.prototype._resetTimeout = function _resetTimeout () {
  if (this.timeout) {
    this.timeout.reset()
  }
}


code:
'use strict'

exports.utils = require('./utils')
exports.constants = require('./constants')
exports.Scheduler = require('./scheduler')
exports.Parser = require('./parser')
exports.Framer = require('./framer')


code:
'use strict'

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

var util = require('util')
var utils = require('./').utils
var OffsetBuffer = require('obuf')
var Transform = require('readable-stream').Transform

function Parser (options) {
  Transform.call(this, {
    readableObjectMode: true
  })

  this.buffer = new OffsetBuffer()
  this.partial = false
  this.waiting = 0

  this.window = options.window

  this.version = null
  this.decompress = null
  this.dead = false
}
module.exports = Parser
util.inherits(Parser, Transform)

Parser.prototype.error = utils.error

Parser.prototype.kill = function kill () {
  this.dead = true
}

Parser.prototype._transform = function transform (data, encoding, cb) {
  if (!this.dead) { this.buffer.push(data) }

  this._consume(cb)
}

Parser.prototype._consume = function _consume (cb) {
  var self = this

  function next (err, frame) {
    if (err) {
      return cb(err)
    }

    if (Array.isArray(frame)) {
      for (var i = 0; i < frame.length; i++) {
        self.push(frame[i])
      }
    } else if (frame) {
      self.push(frame)
    }

    // Consume more packets
    if (!sync) {
      return self._consume(cb)
    }

    process.nextTick(function () {
      self._consume(cb)
    })
  }

  if (this.dead) {
    return cb()
  }

  if (this.buffer.size < this.waiting) {
    // No data at all
    if (this.buffer.size === 0) {
      return cb()
    }

    // Partial DATA frame or something that we can process partially
    if (this.partial) {
      var partial = this.buffer.clone(this.buffer.size)
      this.buffer.skip(partial.size)
      this.waiting -= partial.size

      this.executePartial(partial, next)
      return
    }

    // We shall not do anything until we get all expected data
    return cb()
  }

  var sync = true

  var content = this.buffer.clone(this.waiting)
  this.buffer.skip(this.waiting)

  this.execute(content, next)
  sync = false
}

Parser.prototype.setVersion = function setVersion (version) {
  this.version = version
  this.emit('version', version)
}

Parser.prototype.setCompression = function setCompresion (pair) {
  this.decompress = new transport.utils.LockStream(pair.decompress)
}


code:
'use strict'

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

var assert = require('assert')
var util = require('util')
var debug = require('debug')('spdy:scheduler')
var Readable = require('readable-stream').Readable

/*
 * We create following structure in `pending`:
 * [ [ id = 0 ], [ id = 1 ], [ id = 2 ], [ id = 0 ] ]
 *     chunks      chunks      chunks      chunks
 *     chunks                  chunks
 *     chunks
 *
 * Then on the `.tick()` pass we pick one chunks from each item and remove the
 * item if it is empty:
 *
 * [ [ id = 0 ], [ id = 2 ] ]
 *     chunks      chunks
 *     chunks
 *
 * Writing out: chunks for 0, chunks for 1, chunks for 2, chunks for 0
 *
 * This way data is interleaved between the different streams.
 */

function Scheduler (options) {
  Readable.call(this)

  // Pretty big window by default
  this.window = 0.25

  if (options && options.window) { this.window = options.window }

  this.sync = []
  this.list = []
  this.count = 0
  this.pendingTick = false
}
util.inherits(Scheduler, Readable)
module.exports = Scheduler

// Just for testing, really
Scheduler.create = function create (options) {
  return new Scheduler(options)
}

function insertCompare (a, b) {
  return a.priority === b.priority
    ? a.stream - b.stream
    : b.priority - a.priority
}

Scheduler.prototype.schedule = function schedule (data) {
  var priority = data.priority
  var stream = data.stream
  var chunks = data.chunks

  // Synchronous frames should not be interleaved
  if (priority === false) {
    debug('queue sync', chunks)
    this.sync.push(data)
    this.count += chunks.length

    this._read()
    return
  }

  debug('queue async priority=%d stream=%d', priority, stream, chunks)
  var item = new SchedulerItem(stream, priority)
  var index = utils.binaryLookup(this.list, item, insertCompare)

  // Push new item
  if (index >= this.list.length || insertCompare(this.list[index], item) !== 0) {
    this.list.splice(index, 0, item)
  } else { // Coalesce
    item = this.list[index]
  }

  item.push(data)

  this.count += chunks.length

  this._read()
}

Scheduler.prototype._read = function _read () {
  if (this.count === 0) {
    return
  }

  if (this.pendingTick) {
    return
  }
  this.pendingTick = true

  var self = this
  process.nextTick(function () {
    self.pendingTick = false
    self.tick()
  })
}

Scheduler.prototype.tick = function tick () {
  // No luck for async frames
  if (!this.tickSync()) { return false }

  return this.tickAsync()
}

Scheduler.prototype.tickSync = function tickSync () {
  // Empty sync queue first
  var sync = this.sync
  var res = true
  this.sync = []
  for (var i = 0; i < sync.length; i++) {
    var item = sync[i]
    debug('tick sync pending=%d', this.count, item.chunks)
    for (var j = 0; j < item.chunks.length; j++) {
      this.count--
      // TODO: handle stream backoff properly
      try {
        res = this.push(item.chunks[j])
      } catch (err) {
        this.emit('error', err)
        return false
      }
    }
    debug('after tick sync pending=%d', this.count)

    // TODO(indutny): figure out the way to invoke callback on actual write
    if (item.callback) {
      item.callback(null)
    }
  }
  return res
}

Scheduler.prototype.tickAsync = function tickAsync () {
  var res = true
  var list = this.list
  if (list.length === 0) {
    return res
  }

  var startPriority = list[0].priority
  for (var index = 0; list.length > 0; index++) {
    // Loop index
    index %= list.length
    if (startPriority - list[index].priority > this.window) { index = 0 }
    debug('tick async index=%d start=%d', index, startPriority)

    var current = list[index]
    var item = current.shift()

    if (current.isEmpty()) {
      list.splice(index, 1)
      if (index === 0 && list.length > 0) {
        startPriority = list[0].priority
      }
      index--
    }

    debug('tick async pending=%d', this.count, item.chunks)
    for (var i = 0; i < item.chunks.length; i++) {
      this.count--
      // TODO: handle stream backoff properly
      try {
        res = this.push(item.chunks[i])
      } catch (err) {
        this.emit('error', err)
        return false
      }
    }
    debug('after tick pending=%d', this.count)

    // TODO(indutny): figure out the way to invoke callback on actual write
    if (item.callback) {
      item.callback(null)
    }
    if (!res) { break }
  }

  return res
}

Scheduler.prototype.dump = function dump () {
  this.tickSync()

  // Write everything out
  while (!this.tickAsync()) {
    // Intentional no-op
  }
  assert.strictEqual(this.count, 0)
}

function SchedulerItem (stream, priority) {
  this.stream = stream
  this.priority = priority
  this.queue = []
}

SchedulerItem.prototype.push = function push (chunks) {
  this.queue.push(chunks)
}

SchedulerItem.prototype.shift = function shift () {
  return this.queue.shift()
}

SchedulerItem.prototype.isEmpty = function isEmpty () {
  return this.queue.length === 0
}


code:
'use strict'

var utils = exports

var util = require('util')

function ProtocolError (code, message) {
  this.code = code
  this.message = message
}
util.inherits(ProtocolError, Error)
utils.ProtocolError = ProtocolError

utils.error = function error (code, message) {
  return new ProtocolError(code, message)
}

utils.reverse = function reverse (object) {
  var result = []

  Object.keys(object).forEach(function (key) {
    result[object[key] | 0] = key
  })

  return result
}

// weight [1, 36] <=> priority [0, 7]
// This way weight=16 is preserved and has priority=3
utils.weightToPriority = function weightToPriority (weight) {
  return ((Math.min(35, (weight - 1)) / 35) * 7) | 0
}

utils.priorityToWeight = function priorityToWeight (priority) {
  return (((priority / 7) * 35) | 0) + 1
}

// Copy-Paste from node
exports.addHeaderLine = function addHeaderLine (field, value, dest) {
  field = field.toLowerCase()
  if (/^:/.test(field)) {
    dest[field] = value
    return
  }

  switch (field) {
    // Array headers:
    case 'set-cookie':
      if (dest[field] !== undefined) {
        dest[field].push(value)
      } else {
        dest[field] = [ value ]
      }
      break

    /* eslint-disable max-len */
    // list is taken from:
    /* eslint-enable max-len */
    case 'content-type':
    case 'content-length':
    case 'user-agent':
    case 'referer':
    case 'host':
    case 'authorization':
    case 'proxy-authorization':
    case 'if-modified-since':
    case 'if-unmodified-since':
    case 'from':
    case 'location':
    case 'max-forwards':
      // drop duplicates
      if (dest[field] === undefined) {
        dest[field] = value
      }
      break

    case 'cookie':
      // make semicolon-separated list
      if (dest[field] !== undefined) {
        dest[field] += '; ' + value
      } else {
        dest[field] = value
      }
      break

    default:
      // make comma-separated list
      if (dest[field] !== undefined) {
        dest[field] += ', ' + value
      } else {
        dest[field] = value
      }
  }
}


code:
'use strict'

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

exports.PREFACE_SIZE = 24
exports.PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
exports.PREFACE_BUFFER = Buffer.from(exports.PREFACE)

exports.PING_OPAQUE_SIZE = 8

exports.FRAME_HEADER_SIZE = 9
exports.INITIAL_MAX_FRAME_SIZE = 16384
exports.ABSOLUTE_MAX_FRAME_SIZE = 16777215
exports.HEADER_TABLE_SIZE = 4096
exports.DEFAULT_MAX_HEADER_LIST_SIZE = 80 * 1024 // as in http_parser
exports.MAX_INITIAL_WINDOW_SIZE = 2147483647

exports.DEFAULT_WEIGHT = 16

exports.MAX_CONCURRENT_STREAMS = Infinity

exports.frameType = {
  DATA: 0,
  HEADERS: 1,
  PRIORITY: 2,
  RST_STREAM: 3,
  SETTINGS: 4,
  PUSH_PROMISE: 5,
  PING: 6,
  GOAWAY: 7,
  WINDOW_UPDATE: 8,
  CONTINUATION: 9,

  // Custom
  X_FORWARDED_FOR: 0xde
}

exports.flags = {
  ACK: 0x01, // SETTINGS-only
  END_STREAM: 0x01,
  END_HEADERS: 0x04,
  PADDED: 0x08,
  PRIORITY: 0x20
}

exports.settings = {
  SETTINGS_HEADER_TABLE_SIZE: 0x01,
  SETTINGS_ENABLE_PUSH: 0x02,
  SETTINGS_MAX_CONCURRENT_STREAMS: 0x03,
  SETTINGS_INITIAL_WINDOW_SIZE: 0x04,
  SETTINGS_MAX_FRAME_SIZE: 0x05,
  SETTINGS_MAX_HEADER_LIST_SIZE: 0x06
}

exports.settingsIndex = [
  null,
  'header_table_size',
  'enable_push',
  'max_concurrent_streams',
  'initial_window_size',
  'max_frame_size',
  'max_header_list_size'
]

exports.error = {
  OK: 0,
  NO_ERROR: 0,

  PROTOCOL_ERROR: 1,
  INTERNAL_ERROR: 2,
  FLOW_CONTROL_ERROR: 3,
  SETTINGS_TIMEOUT: 4,

  STREAM_CLOSED: 5,
  INVALID_STREAM: 5,

  FRAME_SIZE_ERROR: 6,
  REFUSED_STREAM: 7,
  CANCEL: 8,
  COMPRESSION_ERROR: 9,
  CONNECT_ERROR: 10,
  ENHANCE_YOUR_CALM: 11,
  INADEQUATE_SECURITY: 12,
  HTTP_1_1_REQUIRED: 13
}
exports.errorByCode = base.utils.reverse(exports.error)

exports.DEFAULT_WINDOW = 64 * 1024 - 1

exports.goaway = exports.error
exports.goawayByCode = Object.assign({}, exports.errorByCode)
exports.goawayByCode[0] = 'OK'


code:
'use strict'

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

var assert = require('assert')
var util = require('util')
var WriteBuffer = require('wbuf')
var OffsetBuffer = require('obuf')
var debug = require('debug')('spdy:framer')
var debugExtra = require('debug')('spdy:framer:extra')

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

  this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
}
util.inherits(Framer, base.Framer)
module.exports = Framer

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

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

Framer.prototype._frame = function _frame (frame, body, callback) {
  debug('id=%d type=%s', frame.id, frame.type)

  var buffer = new WriteBuffer()

  buffer.reserve(constants.FRAME_HEADER_SIZE)
  var len = buffer.skip(3)
  buffer.writeUInt8(constants.frameType[frame.type])
  buffer.writeUInt8(frame.flags)
  buffer.writeUInt32BE(frame.id & 0x7fffffff)

  body(buffer)

  var frameSize = buffer.size - constants.FRAME_HEADER_SIZE
  len.writeUInt24BE(frameSize)

  var chunks = buffer.render()
  var toWrite = {
    stream: frame.id,
    priority: frame.priority === undefined ? false : frame.priority,
    chunks: chunks,
    callback: callback
  }

  if (this.window && frame.type === 'DATA') {
    var self = this
    this._resetTimeout()
    this.window.send.update(-frameSize, function () {
      self._resetTimeout()
      self.schedule(toWrite)
    })
  } else {
    this._resetTimeout()
    this.schedule(toWrite)
  }

  return chunks
}

Framer.prototype._split = function _split (frame) {
  var buf = new OffsetBuffer()
  for (var i = 0; i < frame.chunks.length; i++) { buf.push(frame.chunks[i]) }

  var frames = []
  while (!buf.isEmpty()) {
    // First frame may have reserved bytes in it
    var size = this.maxFrameSize
    if (frames.length === 0) {
      size -= frame.reserve
    }
    size = Math.min(size, buf.size)

    var frameBuf = buf.clone(size)
    buf.skip(size)

    frames.push({
      size: frameBuf.size,
      chunks: frameBuf.toChunks()
    })
  }

  return frames
}

Framer.prototype._continuationFrame = function _continuationFrame (frame,
  body,
  callback) {
  var frames = this._split(frame)

  frames.forEach(function (subFrame, i) {
    var isFirst = i === 0
    var isLast = i === frames.length - 1

    var flags = isLast ? constants.flags.END_HEADERS : 0

    // PRIORITY and friends
    if (isFirst) {
      flags |= frame.flags
    }

    this._frame({
      id: frame.id,
      priority: false,
      type: isFirst ? frame.type : 'CONTINUATION',
      flags: flags
    }, function (buf) {
      // Fill those reserved bytes
      if (isFirst && body) { body(buf) }

      buf.reserve(subFrame.size)
      for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
    }, isLast ? callback : null)
  }, this)

  if (frames.length === 0) {
    this._frame({
      id: frame.id,
      priority: false,
      type: frame.type,
      flags: frame.flags | constants.flags.END_HEADERS
    }, function (buf) {
      if (body) { body(buf) }
    }, callback)
  }
}

Framer.prototype._compressHeaders = function _compressHeaders (headers,
  pairs,
  callback) {
  Object.keys(headers || {}).forEach(function (name) {
    var lowName = name.toLowerCase()

    // Not allowed in HTTP2
    switch (lowName) {
      case 'host':
      case 'connection':
      case 'keep-alive':
      case 'proxy-connection':
      case 'transfer-encoding':
      case 'upgrade':
        return
    }

    // Should be in `pairs`
    if (/^:/.test(lowName)) {
      return
    }

    // Do not compress, or index Cookie field (for security reasons)
    var neverIndex = lowName === 'cookie' || lowName === 'set-cookie'

    var value = headers[name]
    if (Array.isArray(value)) {
      for (var i = 0; i < value.length; i++) {
        pairs.push({
          name: lowName,
          value: value[i] + '',
          neverIndex: neverIndex,
          huffman: !neverIndex
        })
      }
    } else {
      pairs.push({
        name: lowName,
        value: value + '',
        neverIndex: neverIndex,
        huffman: !neverIndex
      })
    }
  })

  assert(this.compress !== null, 'Framer version not initialized')
  debugExtra('compressing headers=%j', pairs)
  this.compress.write([ pairs ], callback)
}

Framer.prototype._isDefaultPriority = function _isDefaultPriority (priority) {
  if (!priority) { return true }

  return !priority.parent &&
         priority.weight === constants.DEFAULT &&
         !priority.exclusive
}

Framer.prototype._defaultHeaders = function _defaultHeaders (frame, pairs) {
  if (!frame.path) {
    throw new Error('`path` is required frame argument')
  }

  pairs.push({
    name: ':method',
    value: frame.method || base.constants.DEFAULT_METHOD
  })
  pairs.push({ name: ':path', value: frame.path })
  pairs.push({ name: ':scheme', value: frame.scheme || 'https' })
  pairs.push({
    name: ':authority',
    value: frame.host ||
           (frame.headers && frame.headers.host) ||
           base.constants.DEFAULT_HOST
  })
}

Framer.prototype._headersFrame = function _headersFrame (kind, frame, callback) {
  var pairs = []

  if (kind === 'request') {
    this._defaultHeaders(frame, pairs)
  } else if (kind === 'response') {
    pairs.push({ name: ':status', value: (frame.status || 200) + '' })
  }

  var self = this
  this._compressHeaders(frame.headers, pairs, function (err, chunks) {
    if (err) {
      if (callback) {
        return callback(err)
      } else {
        return self.emit('error', err)
      }
    }

    var reserve = 0

    // If priority info is present, and the values are not default ones
    // reserve space for the priority info and add PRIORITY flag
    var priority = frame.priority
    if (!self._isDefaultPriority(priority)) { reserve = 5 }

    var flags = reserve === 0 ? 0 : constants.flags.PRIORITY

    // Mostly for testing
    if (frame.fin) {
      flags |= constants.flags.END_STREAM
    }

    self._continuationFrame({
      id: frame.id,
      type: 'HEADERS',
      flags: flags,
      reserve: reserve,
      chunks: chunks
    }, function (buf) {
      if (reserve === 0) {
        return
      }

      buf.writeUInt32BE(((priority.exclusive ? 0x80000000 : 0) |
                         priority.parent) >>> 0)
      buf.writeUInt8((priority.weight | 0) - 1)
    }, callback)
  })
}

Framer.prototype.requestFrame = function requestFrame (frame, callback) {
  return this._headersFrame('request', frame, callback)
}

Framer.prototype.responseFrame = function responseFrame (frame, callback) {
  return this._headersFrame('response', frame, callback)
}

Framer.prototype.headersFrame = function headersFrame (frame, callback) {
  return this._headersFrame('headers', frame, callback)
}

Framer.prototype.pushFrame = function pushFrame (frame, callback) {
  var self = this

  function compress (headers, pairs, callback) {
    self._compressHeaders(headers, pairs, function (err, chunks) {
      if (err) {
        if (callback) {
          return callback(err)
        } else {
          return self.emit('error', err)
        }
      }

      callback(chunks)
    })
  }

  function sendPromise (chunks) {
    self._continuationFrame({
      id: frame.id,
      type: 'PUSH_PROMISE',
      reserve: 4,
      chunks: chunks
    }, function (buf) {
      buf.writeUInt32BE(frame.promisedId)
    })
  }

  function sendResponse (chunks, callback) {
    var priority = frame.priority
    var isDefaultPriority = self._isDefaultPriority(priority)
    var flags = isDefaultPriority ? 0 : constants.flags.PRIORITY

    // Mostly for testing
    if (frame.fin) {
      flags |= constants.flags.END_STREAM
    }

    self._continuationFrame({
      id: frame.promisedId,
      type: 'HEADERS',
      flags: flags,
      reserve: isDefaultPriority ? 0 : 5,
      chunks: chunks
    }, function (buf) {
      if (isDefaultPriority) {
        return
      }

      buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
                        priority.parent)
      buf.writeUInt8((priority.weight | 0) - 1)
    }, callback)
  }

  this._checkPush(function (err) {
    if (err) {
      return callback(err)
    }

    var pairs = {
      promise: [],
      response: []
    }

    self._defaultHeaders(frame, pairs.promise)
    pairs.response.push({ name: ':status', value: (frame.status || 200) + '' })

    compress(frame.headers, pairs.promise, function (promiseChunks) {
      sendPromise(promiseChunks)
      if (frame.response === false) {
        return callback(null)
      }
      compress(frame.response, pairs.response, function (responseChunks) {
        sendResponse(responseChunks, callback)
      })
    })
  })
}

Framer.prototype.priorityFrame = function priorityFrame (frame, callback) {
  this._frame({
    id: frame.id,
    priority: false,
    type: 'PRIORITY',
    flags: 0
  }, function (buf) {
    var priority = frame.priority
    buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
                      priority.parent)
    buf.writeUInt8((priority.weight | 0) - 1)
  }, callback)
}

Framer.prototype.dataFrame = function dataFrame (frame, callback) {
  var frames = this._split({
    reserve: 0,
    chunks: [ frame.data ]
  })

  var fin = frame.fin ? constants.flags.END_STREAM : 0

  var self = this
  frames.forEach(function (subFrame, i) {
    var isLast = i === frames.length - 1
    var flags = 0
    if (isLast) {
      flags |= fin
    }

    self._frame({
      id: frame.id,
      priority: frame.priority,
      type: 'DATA',
      flags: flags
    }, function (buf) {
      buf.reserve(subFrame.size)
      for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
    }, isLast ? callback : null)
  })

  // Empty DATA
  if (frames.length === 0) {
    this._frame({
      id: frame.id,
      priority: frame.priority,
      type: 'DATA',
      flags: fin
    }, function (buf) {
      // No-op
    }, callback)
  }
}

Framer.prototype.pingFrame = function pingFrame (frame, callback) {
  this._frame({
    id: 0,
    type: 'PING',
    flags: frame.ack ? constants.flags.ACK : 0
  }, function (buf) {
    buf.copyFrom(frame.opaque)
  }, callback)
}

Framer.prototype.rstFrame = function rstFrame (frame, callback) {
  this._frame({
    id: frame.id,
    type: 'RST_STREAM',
    flags: 0
  }, function (buf) {
    buf.writeUInt32BE(constants.error[frame.code])
  }, callback)
}

Framer.prototype.prefaceFrame = function prefaceFrame (callback) {
  debug('preface')
  this._resetTimeout()
  this.schedule({
    stream: 0,
    priority: false,
    chunks: [ constants.PREFACE_BUFFER ],
    callback: callback
  })
}

Framer.prototype.settingsFrame = function settingsFrame (options, callback) {
  var key = JSON.stringify(options)

  var settings = Framer.settingsCache[key]
  if (settings) {
    debug('cached settings')
    this._resetTimeout()
    this.schedule({
      id: 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 bodySize = params.length * 6

  var chunks = this._frame({
    id: 0,
    type: 'SETTINGS',
    flags: 0
  }, function (buffer) {
    buffer.reserve(bodySize)
    for (var i = 0; i < params.length; i++) {
      var param = params[i]

      buffer.writeUInt16BE(param.key)
      buffer.writeUInt32BE(param.value)
    }
  }, callback)

  Framer.settingsCache[key] = chunks
}
Framer.settingsCache = {}

Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) {
  /* var chunks = */ this._frame({
    id: 0,
    type: 'SETTINGS',
    flags: constants.flags.ACK
  }, function (buffer) {
    // No-op
  }, callback)
}

Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame,
  callback) {
  this._frame({
    id: frame.id,
    type: 'WINDOW_UPDATE',
    flags: 0
  }, function (buffer) {
    buffer.reserve(4)
    buffer.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)
    // Code
    buf.writeUInt32BE(constants.goaway[frame.code])

    // Extra debugging information
    if (frame.extra) { buf.write(frame.extra) }
  }, callback)
}

Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) {
  this._frame({
    type: 'X_FORWARDED_FOR',
    id: 0,
    flags: 0
  }, function (buf) {
    buf.write(frame.host)
  }, callback)
}


code:
'use strict'

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

var hpack = require('hpack.js')

function Pool () {
}
module.exports = Pool

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

Pool.prototype.get = function get (version) {
  var options = {
    table: {
      maxSize: constants.HEADER_TABLE_SIZE
    }
  }

  var compress = hpack.compressor.create(options)
  var decompress = hpack.decompressor.create(options)

  return {
    version: version,

    compress: compress,
    decompress: decompress
  }
}

Pool.prototype.put = function put () {
}


code:
'use strict'

exports.name = 'h2'

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