'use strict' var util = require('util') var EventEmitter = require('events').EventEmitter var debug = { server: require('debug')('spdy:window:server'), client: require('debug')('spdy:window:client') } function Side (window, name, options) { EventEmitter.call(this) this.name = name this.window = window this.current = options.size this.max = options.size this.limit = options.max this.lowWaterMark = options.lowWaterMark === undefined ? this.max / 2 : options.lowWaterMark this._refilling = false this._refillQueue = [] } util.inherits(Side, EventEmitter) Side.prototype.setMax = function setMax (max) { this.window.debug('id=%d side=%s setMax=%d', this.window.id, this.name, max) this.max = max this.lowWaterMark = this.max / 2 } Side.prototype.updateMax = function updateMax (max) { var delta = max - this.max this.window.debug('id=%d side=%s updateMax=%d delta=%d', this.window.id, this.name, max, delta) this.max = max this.lowWaterMark = max / 2 this.update(delta) } Side.prototype.setLowWaterMark = function setLowWaterMark (lwm) { this.lowWaterMark = lwm } Side.prototype.update = function update (size, callback) { // Not enough space for the update, wait for refill if (size <= 0 && callback && this.isEmpty()) { this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]', this.window.id, this.name, -size, this.current, this.max) this._refillQueue.push({ size: size, callback: callback }) return } this.current += size if (this.current > this.limit) { this.emit('overflow') return } this.window.debug('id=%d side=%s update by=%d [%d/%d]', this.window.id, this.name, size, this.current, this.max) // Time to send WINDOW_UPDATE if (size < 0 && this.isDraining()) { this.window.debug('id=%d side=%s drained', this.window.id, this.name) this.emit('drain') } // Time to write if (size > 0 && this.current > 0 && this.current <= size) { this.window.debug('id=%d side=%s full', this.window.id, this.name) this.emit('full') } this._processRefillQueue() if (callback) { process.nextTick(callback) } } Side.prototype.getCurrent = function getCurrent () { return this.current } Side.prototype.getMax = function getMax () { return this.max } Side.prototype.getDelta = function getDelta () { return this.max - this.current } Side.prototype.isDraining = function isDraining () { return this.current <= this.lowWaterMark } Side.prototype.isEmpty = function isEmpty () { return this.current <= 0 } // Private Side.prototype._processRefillQueue = function _processRefillQueue () { // Prevent recursion if (this._refilling) { return } this._refilling = true while (this._refillQueue.length > 0) { var item = this._refillQueue[0] if (this.isEmpty()) { break } this.window.debug('id=%d side=%s refilled for size=%d', this.window.id, this.name, -item.size) this._refillQueue.shift() this.update(item.size, item.callback) } this._refilling = false } function Window (options) { this.id = options.id this.isServer = options.isServer this.debug = this.isServer ? debug.server : debug.client this.recv = new Side(this, 'recv', options.recv) this.send = new Side(this, 'send', options.send) } module.exports = Window Window.prototype.clone = function clone (id) { return new Window({ id: id, isServer: this.isServer, recv: { size: this.recv.max, max: this.recv.limit, lowWaterMark: this.recv.lowWaterMark }, send: { size: this.send.max, max: this.send.limit, lowWaterMark: this.send.lowWaterMark } }) }