text: /*!
   * ws: a node.js websocket client
   * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
   * MIT Licensed
   */
  
  var errorCodes = require('./error-codes');
  var BufferPool = require('./buffer-pool');
  var bufferUtil = require('./buffer-util');
  var PerMessageDeflate = require('./per-message-deflate');
  
  var MAX_PAYLOAD = 1024 * 1024 * 10;
  
  /**
   * HyBi Receiver implementation
   */
  
  function Receiver (extensions, maxPayload) {
    if (this instanceof Receiver === false) {
      throw new TypeError('Classes can\'t be function-called');
    }
    maxPayload = maxPayload || MAX_PAYLOAD;
    if(typeof extensions==='number'){
      maxPayload=extensions;
      extensions={};
    }
  
  
    // memory pool for fragmented messages
    var fragmentedPoolPrevUsed = -1;
    this.fragmentedBufferPool = new BufferPool(1024, function(db, length) {
      return db.used + length;
    }, function(db) {
      return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ?
        Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) :
        db.used;
    });
  
    // memory pool for unfragmented messages
    var unfragmentedPoolPrevUsed = -1;
    this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) {
      return db.used + length;
    }, function(db) {
      return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ?
        Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) :
        db.used;
    });
    this.extensions = extensions || {};
    this.maxPayload = maxPayload || 0;
    this.currentPayloadLength = 0;
    this._allDataLength = 0;
    this._readDataLength = 0;
    this._processedLength = 0;
    this.state = {
      activeFragmentedOperation: null,
      lastFragment: false,
      masked: false,
      opcode: 0,
      fragmentedOperation: false
    };
    this.overflow = [];
    this.headerBuffer = new Buffer(10);
    this.expectOffset = 0;
    this.expectBuffer = null;
    this.expectHandler = null;
    this.currentMessage = [];
    this.currentMessageLength = 0;
    this.messageHandlers = [];
    this.expectHeader(2, this.processPacket);
    this.dead = false;
    this.processing = false;
  
    this.onerror = function() {};
    this.onData = function() {};
    this.onclose = function() {};
    this.onping = function() {};
    this.onpong = function() {};
  }
  
  module.exports = Receiver;
  
  /**
   * Add new data to the parser.
   *
   * @api public
   */
  
  Receiver.prototype.add = function(data) {
    if (this.dead) return;
    var dataLength = data.length;
    if (dataLength == 0) return;
    this._allDataLength += dataLength;
    this.existsCacheData = true;
    if (this.expectBuffer == null) {
      this.overflow.push(data);
      return;
    }
    var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset);
    fastCopy(toRead, data, this.expectBuffer, this.expectOffset);
    this._readDataLength += toRead;
    this.expectOffset += toRead;
    if (toRead < dataLength) {
      this.overflow.push(data.slice(toRead));
    }
    while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) {
      var bufferForHandler = this.expectBuffer;
      this.expectBuffer = null;
      this.expectOffset = 0;
      this.expectHandler.call(this, bufferForHandler);
    }
    // toWrite, toRead
    return dataLength - (this._allDataLength - this._processedLength);
  };
  
  /**
   * Releases all resources used by the receiver.
   *
   * @api public
   */
  
  Receiver.prototype.cleanup = function() {
    this.dead = true;
    this.overflow = null;
    this.headerBuffer = null;
    this.expectBuffer = null;
    this.expectHandler = null;
    this.unfragmentedBufferPool = null;
    this.fragmentedBufferPool = null;
    this.state = null;
    this.currentMessage = null;
    this.onerror = null;
    this.onData = null;
    this.onclose = null;
    this.onping = null;
    this.onpong = null;
  };
  
  /**
   * Waits for a certain amount of header bytes to be available, then fires a callback.
   *
   * @api private
   */
  
  Receiver.prototype.expectHeader = function(length, handler) {
    if (length == 0) {
      handler(null);
      return;
    }
    this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length);
    this.expectHandler = handler;
    var toRead = length;
    while (toRead > 0 && this.overflow.length > 0) {
      var fromOverflow = this.overflow.pop();
      if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead));
      var read = Math.min(fromOverflow.length, toRead);
      fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset);
      this.expectOffset += read;
      this._readDataLength += read;
      toRead -= read;
    }
  };
  
  /**
   * Waits for a certain amount of data bytes to be available, then fires a callback.
   *
   * @api private
   */
  
  Receiver.prototype.expectData = function(length, handler) {
    if (length == 0) {
      handler(null);
      return;
    }
    this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation);
    this.expectHandler = handler;
    var toRead = length;
    while (toRead > 0 && this.overflow.length > 0) {
      var fromOverflow = this.overflow.pop();
      if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead));
      var read = Math.min(fromOverflow.length, toRead);
      fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset);
      this.expectOffset += read;
      this._readDataLength += read;
      toRead -= read;
    }
  };
  
  /**
   * Allocates memory from the buffer pool.
   *
   * @api private
   */
  
  Receiver.prototype.allocateFromPool = function(length, isFragmented) {
    return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length);
  };
  
  /**
   * Start processing a new packet.
   *
   * @api private
   */
  
  Receiver.prototype.processPacket = function (data) {
    if (this.extensions[PerMessageDeflate.extensionName]) {
      if ((data[0] & 0x30) != 0) {
        this.error('reserved fields (2, 3) must be empty', 1002);
        return;
      }
    } else {
      if ((data[0] & 0x70) != 0) {
        this.error('reserved fields must be empty', 1002);
        return;
      }
    }
    this.state.lastFragment = (data[0] & 0x80) == 0x80;
    this.state.masked = (data[1] & 0x80) == 0x80;
    var compressed = (data[0] & 0x40) == 0x40;
    var opcode = data[0] & 0xf;
    if (opcode === 0) {
      if (compressed) {
        this.error('continuation frame cannot have the Per-message Compressed bits', 1002);
        return;
      }
      // continuation frame
      this.state.fragmentedOperation = true;
      this.state.opcode = this.state.activeFragmentedOperation;
      if (!(this.state.opcode == 1 || this.state.opcode == 2)) {
        this.error('continuation frame cannot follow current opcode', 1002);
        return;
      }
    }
    else {
      if (opcode < 3 && this.state.activeFragmentedOperation != null) {
        this.error('data frames after the initial data frame must have opcode 0', 1002);
        return;
      }
      if (opcode >= 8 && compressed) {
        this.error('control frames cannot have the Per-message Compressed bits', 1002);
        return;
      }
      this.state.compressed = compressed;
      this.state.opcode = opcode;
      if (this.state.lastFragment === false) {
        this.state.fragmentedOperation = true;
        this.state.activeFragmentedOperation = opcode;
      }
      else this.state.fragmentedOperation = false;
    }
    var handler = opcodes[this.state.opcode];
    if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002);
    else {
      handler.start.call(this, data);
    }
  };
  
  /**
   * Endprocessing a packet.
   *
   * @api private
   */
  
  Receiver.prototype.endPacket = function() {
    if (this.dead) return;
    if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true);
    else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true);
    this.expectOffset = 0;
    this.expectBuffer = null;
    this.expectHandler = null;
    if (this.state.lastFragment && this.state.opcode === this.state.activeFragmentedOperation) {
      // end current fragmented operation
      this.state.activeFragmentedOperation = null;
    }
    this.currentPayloadLength = 0;
    this.state.lastFragment = false;
    this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0;
    this.state.masked = false;
    this._processedLength = this._readDataLength;
    this.existsCacheData = this._processedLength !== this._allDataLength;
    this.expectHeader(2, this.processPacket);
  };
  
  /**
   * Reset the parser state.
   *
   * @api private
   */
  
  Receiver.prototype.reset = function() {
    if (this.dead) return;
    this.state = {
      activeFragmentedOperation: null,
      lastFragment: false,
      masked: false,
      opcode: 0,
      fragmentedOperation: false
    };
    this.fragmentedBufferPool.reset(true);
    this.unfragmentedBufferPool.reset(true);
    this.expectOffset = 0;
    this.expectBuffer = null;
    this.expectHandler = null;
    this.overflow = [];
    this.currentMessage = [];
    this.currentMessageLength = 0;
    this.messageHandlers = [];
    this.currentPayloadLength = 0;
  };
  
  /**
   * Unmask received data.
   *
   * @api private
   */
  
  Receiver.prototype.unmask = function (mask, buf, binary) {
    if (mask != null && buf != null) bufferUtil.unmask(buf, mask);
    if (binary) return buf;
    return buf != null ? buf.toString('utf8') : '';
  };
  
  /**
   * Handles an error
   *
   * @api private
   */
  
  Receiver.prototype.error = function (reason, protocolErrorCode) {
    if (this.dead) return;
    this.reset();
    if(typeof reason == 'string'){
      this.onerror(new Error(reason), protocolErrorCode);
    }
    else if(reason.constructor == Error){
      this.onerror(reason, protocolErrorCode);
    }
    else{
      this.onerror(new Error('An error occured'),protocolErrorCode);
    }
    return this;
  };
  
  /**
   * Execute message handler buffers
   *
   * @api private
   */
  
  Receiver.prototype.flush = function() {
    if (this.processing || this.dead) return;
  
    var handler = this.messageHandlers.shift();
    if (!handler) return;
  
    this.processing = true;
    var self = this;
  
    handler(function() {
      self.processing = false;
      self.flush();
    });
  };
  
  /**
   * Apply extensions to message
   *
   * @api private
   */
  
  Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) {
    var self = this;
    compressed = compressed && this.extensions[PerMessageDeflate.extensionName];
    if (compressed) {
      compressed.decompress(messageBuffer, fin, function(err, buffer) {
        if (self.dead) return;
        if (err) {
          callback(new Error('invalid compressed data'));
          return;
        }
        callback(null, buffer);
      });
    } else {
      callback(null, messageBuffer);
    }
  };
  
  /**
  * Checks payload size, disconnects socket when it exceeds maxPayload
  *
  * @api private
  */
  Receiver.prototype.maxPayloadExceeded = function(length) {
    if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) {
      return false;
    }
    var fullLength = this.currentPayloadLength + length;
    if (fullLength < this.maxPayload) {
      this.currentPayloadLength = fullLength;
      return false;
    }
    this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009);
    this.messageBuffer=[];
    this.cleanup();
  
    return true;
  };
  
  /**
   * Buffer utilities
   */
  
  function readUInt16BE(start) {
    return (this[start]<<8) +
           this[start+1];
  }
  
  function readUInt32BE(start) {
    return (this[start]<<24) +
           (this[start+1]<<16) +
           (this[start+2]<<8) +
           this[start+3];
  }
  
  function fastCopy(length, srcBuffer, dstBuffer, dstOffset) {
    srcBuffer.copy(dstBuffer, dstOffset, 0, length);
  }
  
  function clone(obj) {
    var cloned = {};
    for (var k in obj) {
      if (obj.hasOwnProperty(k)) {
        cloned[k] = obj[k];
      }
    }
    return cloned;
  }
  
  /**
   * Opcode handlers
   */
  
  var opcodes = {
    // text
    '1': {
      start: function(data) {
        var self = this;
        // decode length
        var firstLength = data[1] & 0x7f;
        if (firstLength < 126) {
          if (self.maxPayloadExceeded(firstLength)){
            self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
            return;
          }
          opcodes['1'].getData.call(self, firstLength);
        }
        else if (firstLength == 126) {
          self.expectHeader(2, function(data) {
            var length = readUInt16BE.call(data, 0);
            if (self.maxPayloadExceeded(length)){
              self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
              return;
            }
            opcodes['1'].getData.call(self, length);
          });
        }
        else if (firstLength == 127) {
          self.expectHeader(8, function(data) {
            if (readUInt32BE.call(data, 0) != 0) {
              self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
              return;
            }
            var length = readUInt32BE.call(data, 4);
            if (self.maxPayloadExceeded(length)){
              self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
              return;
            }
            opcodes['1'].getData.call(self, readUInt32BE.call(data, 4));
          });
        }
      },
      getData: function(length) {
        var self = this;
        if (self.state.masked) {
          self.expectHeader(4, function(data) {
            var mask = data;
            self.expectData(length, function(data) {
              opcodes['1'].finish.call(self, mask, data, length);
            });
          });
        }
        else {
          self.expectData(length, function(data) {
            opcodes['1'].finish.call(self, null, data, length);
          });
        }
      },
      finish: function(mask, data, length) {
        var self = this;
        var packet = this.unmask(mask, data, true) || new Buffer(0);
        var state = clone(this.state);
        this.messageHandlers.push(function(callback) {
          self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
            if (err) {
              if(err.type===1009){
                return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009);
              }
              return self.error(err.message, 1007);
            }
            if (buffer != null) {
              if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){
                self.currentMessage.push(buffer);
              }
              else{
                self.currentMessage=null;
                self.currentMessage = [];
                self.currentMessageLength = 0;
                self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009);
                return;
              }
              self.currentMessageLength += buffer.length;
            }
            if (state.lastFragment) {
              var messageBuffer = Buffer.concat(self.currentMessage);
              self.currentMessage = [];
              self.currentMessageLength = 0;
              self.onData(messageBuffer, {length: length, mask: !!mask, compressed: state.compressed, opcode: 1});
            }
            callback();
          });
        });
        this.flush();
        this.endPacket();
      }
    },
    // binary
    '2': {
      start: function(data) {
        var self = this;
        // decode length
        var firstLength = data[1] & 0x7f;
        if (firstLength < 126) {
          if (self.maxPayloadExceeded(firstLength)){
            self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
            return;
          }
          opcodes['2'].getData.call(self, firstLength);
        }
        else if (firstLength == 126) {
          self.expectHeader(2, function(data) {
            var length = readUInt16BE.call(data, 0);
            if (self.maxPayloadExceeded(length)){
              self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
              return;
            }
            opcodes['2'].getData.call(self, length);
          });
        }
        else if (firstLength == 127) {
          self.expectHeader(8, function(data) {
            if (readUInt32BE.call(data, 0) != 0) {
              self.error('packets with length spanning more than 32 bit is currently not supported', 1008);
              return;
            }
            var length = readUInt32BE.call(data, 4, true);
            if (self.maxPayloadExceeded(length)){
              self.error('Max payload exceeded in compressed text message. Aborting...', 1009);
              return;
            }
            opcodes['2'].getData.call(self, length);
          });
        }
      },
      getData: function(length) {
        var self = this;
        if (self.state.masked) {
          self.expectHeader(4, function(data) {
            var mask = data;
            self.expectData(length, function(data) {
              opcodes['2'].finish.call(self, mask, data, length);
            });
          });
        }
        else {
          self.expectData(length, function(data) {
            opcodes['2'].finish.call(self, null, data, length);
          });
        }
      },
      finish: function(mask, data, length) {
        var self = this;
        var packet = this.unmask(mask, data, true) || new Buffer(0);
        var state = clone(this.state);
        this.messageHandlers.push(function(callback) {
          self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) {
            if (err) {
              if(err.type===1009){
                return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009);
              }
              return self.error(err.message, 1007);
            }
            if (buffer != null) {
              if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){
                self.currentMessage.push(buffer);
              }
              else{
                self.currentMessage=null;
                self.currentMessage = [];
                self.currentMessageLength = 0;
                self.error(new Error('Maximum payload exceeded'), 1009);
                return;
              }
              self.currentMessageLength += buffer.length;
            }
            if (state.lastFragment) {
              var messageBuffer = Buffer.concat(self.currentMessage);
              self.currentMessage = [];
              self.currentMessageLength = 0;
              self.onData(messageBuffer, {length: length, mask: !!mask, compressed: state.compressed, opcode: 2});
            }
            callback();
          });
        });
        this.flush();
        this.endPacket();
      }
    },
    // close
    '8': {
      start: function(data) {
        var self = this;
        if (self.state.lastFragment == false) {
          self.error('fragmented close is not supported', 1002);
          return;
        }
  
        // decode length
        var firstLength = data[1] & 0x7f;
        if (firstLength < 126) {
          opcodes['8'].getData.call(self, firstLength);
        }
        else {
          self.error('control frames cannot have more than 125 bytes of data', 1002);
        }
      },
      getData: function(length) {
        var self = this;
        if (self.state.masked) {
          self.expectHeader(4, function(data) {
            var mask = data;
            self.expectData(length, function(data) {
              opcodes['8'].finish.call(self, mask, data);
            });
          });
        }
        else {
          self.expectData(length, function(data) {
            opcodes['8'].finish.call(self, null, data);
          });
        }
      },
      finish: function(mask, data) {
        var self = this;
        data = self.unmask(mask, data, true);
  
        var state = clone(this.state);
        this.messageHandlers.push(function() {
          if (data && data.length == 1) {
            self.error('close packets with data must be at least two bytes long', 1002);
            return;
          }
          var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000;
          if (!errorCodes.isValidErrorCode(code)) {
            self.error('invalid error code', 1002);
            return;
          }
          var message = '';
          if (data && data.length > 2) {
            var messageBuffer = data.slice(2);
            message = messageBuffer.toString('utf8');
          }
          self.onclose(code, message, {masked: state.masked});
          self.reset();
        });
        this.flush();
      }
    },
    // ping
    '9': {
      start: function(data) {
        var self = this;
        if (self.state.lastFragment == false) {
          self.error('fragmented ping is not supported', 1002);
          return;
        }
  
        // decode length
        var firstLength = data[1] & 0x7f;
        if (firstLength < 126) {
          opcodes['9'].getData.call(self, firstLength);
        }
        else {
          self.error('control frames cannot have more than 125 bytes of data', 1002);
        }
      },
      getData: function(length) {
        var self = this;
        if (self.state.masked) {
          self.expectHeader(4, function(data) {
            var mask = data;
            self.expectData(length, function(data) {
              opcodes['9'].finish.call(self, mask, data);
            });
          });
        }
        else {
          self.expectData(length, function(data) {
            opcodes['9'].finish.call(self, null, data);
          });
        }
      },
      finish: function(mask, data) {
        var self = this;
        data = this.unmask(mask, data, true);
        var state = clone(this.state);
        this.messageHandlers.push(function(callback) {
          self.onping(data, {masked: state.masked, binary: true});
          callback();
        });
        this.flush();
        this.endPacket();
      }
    },
    // pong
    '10': {
      start: function(data) {
        var self = this;
        if (self.state.lastFragment == false) {
          self.error('fragmented pong is not supported', 1002);
          return;
        }
  
        // decode length
        var firstLength = data[1] & 0x7f;
        if (firstLength < 126) {
          opcodes['10'].getData.call(self, firstLength);
        }
        else {
          self.error('control frames cannot have more than 125 bytes of data', 1002);
        }
      },
      getData: function(length) {
        var self = this;
        if (this.state.masked) {
          this.expectHeader(4, function(data) {
            var mask = data;
            self.expectData(length, function(data) {
              opcodes['10'].finish.call(self, mask, data);
            });
          });
        }
        else {
          this.expectData(length, function(data) {
            opcodes['10'].finish.call(self, null, data);
          });
        }
      },
      finish: function(mask, data) {
        var self = this;
        data = self.unmask(mask, data, true);
        var state = clone(this.state);
        this.messageHandlers.push(function(callback) {
          self.onpong(data, {masked: state.masked, binary: true});
          callback();
        });
        this.flush();
        this.endPacket();
      }
    }
  };
  