text: /*----------ws License and Copyright Notices--------
  ws
  http://einaros.github.com/ws
  -----------------------------------
  This file is based on or incorporates material from the projects listed below 
  (collectively, “Third Party Code”). Microsoft is not the original author 
  of the Third Party Code.   
  The original copyright notice and the MIT license, under which Microsoft received 
  such Third Party Code, are set forth below.  
  Such licenses and notices are provided for informational purposes only.  Microsoft, 
  not the third party, licenses the Third Party Code to you under the terms 
  of the Microsoft Reference Source Code License set forth below. 
  Microsoft reserves all other rights not expressly granted under this agreement, 
  whether by implication, estoppel or otherwise.  
  ----------------------------------------
  ws: a node.js websocket client
  Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
  http://einaros.github.com/ws
  
  Portions Copyright Microsoft Open Technologies, Inc.
  
  ---------------------------------------
  Copyright 2012 Microsoft Open Technologies, Inc.
  
  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.
  
  ---------------------------------
  
  Provided for Informational Purposes Only
  MIT License
  
  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.
  
  --End of ws Licenses and Copyright Notices------*/
  
  var events = require('events')
    , util = require('util')
    , EventEmitter = events.EventEmitter
    , Options = require('options')
    , ErrorCodes = require('./ErrorCodes')
    , bufferUtil = require('./BufferUtil').BufferUtil;
  
  /**
   * HyBi Sender implementation
   */
  
  function Sender (socket, options) {
    options = new Options({}).merge(options);
    this._socket = socket;
    this.firstFragment = true;
  }
  
  /**
   * Inherits from EventEmitter.
   */
  
  util.inherits(Sender, events.EventEmitter);
  
  /**
   * Sends a close instruction to the remote party.
   *
   * @api public
   */
  
  Sender.prototype.close = function(code, data, mask) {
    if (typeof code !== 'undefined') {
      if (typeof code !== 'number' ||
        !ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number');
    }
    code = code || 1000;
    var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0));
    writeUInt16BE.call(dataBuffer, code, 0);
    if (dataBuffer.length > 2) dataBuffer.write(data, 2);
    this.frameAndSend(0x8, dataBuffer, true, mask);
  }
  
  /**
   * Sends a ping message to the remote party.
   *
   * @api public
   */
  
  Sender.prototype.ping = function(data, options) {
    var mask = options && options.mask;
    this.frameAndSend(0x9, data || '', true, mask);
  }
  
  /**
   * Sends a pong message to the remote party.
   *
   * @api public
   */
  
  Sender.prototype.pong = function(data, options) {
    var mask = options && options.mask;
    this.frameAndSend(0xa, data || '', true, mask);
  }
  
  /**
   * Sends text or binary data to the remote party.
   *
   * @api public
   */
  
  Sender.prototype.send = function(data, options, cb) {
    var finalFragment = options && options.fin === false ? false : true;
    var mask = options && options.mask;
    var opcode = options && options.binary ? 2 : 1;
    if (this.firstFragment === false) opcode = 0;
    else this.firstFragment = false;
    if (finalFragment) this.firstFragment = true
    this.frameAndSend(opcode, data, finalFragment, mask, cb);
  }
  
  /**
   * Frames and sends a piece of data according to the HyBi WebSocket protocol.
   *
   * @api private
   */
  
  Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, cb) {
    var canModifyData = false;
  
    if (!data) {
      try {
        this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0]), 'binary', cb);
      }
      catch (e) {
        if (typeof cb == 'function') cb(e);
        else this.emit('error', e);
      }
      return;
    }
  
    if (!Buffer.isBuffer(data)) {
      canModifyData = true;
      data = (data && typeof data.buffer !== 'undefined') ? getArrayBuffer(data.buffer) : new Buffer(data);
    }
  
    var dataLength = data.length
      , dataOffset = maskData ? 6 : 2
      , secondByte = dataLength;
  
    if (dataLength >= 65536) {
      dataOffset += 8;
      secondByte = 127;
    }
    else if (dataLength > 125) {
      dataOffset += 2;
      secondByte = 126;
    }
  
    var totalLength = (maskData && !canModifyData) ? dataLength + dataOffset : dataOffset;
    var outputBuffer = new Buffer(totalLength);
  
    outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode;
  
    switch (secondByte) {
      case 126:
        writeUInt16BE.call(outputBuffer, dataLength, 2);
        break;
      case 127:
        writeUInt32BE.call(outputBuffer, 0, 2);
        writeUInt32BE.call(outputBuffer, dataLength, 6);
    }
  
    var sendsDone = 0;
    var cbCaller = function() { if (++sendsDone == 2 && typeof cb == 'function') cb(null); }
  
    if (maskData) {
      outputBuffer[1] = secondByte | 0x80;
      var mask = this._randomMask || (this._randomMask = getRandomMask());
      outputBuffer[dataOffset - 4] = mask[0];
      outputBuffer[dataOffset - 3] = mask[1];
      outputBuffer[dataOffset - 2] = mask[2];
      outputBuffer[dataOffset - 1] = mask[3];
      if (!canModifyData) {
        bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength);
        try {
          this._socket.write(outputBuffer, 'binary', cb);
        }
        catch (e) {
          if (typeof cb == 'function') cb(e);
          else this.emit('error', e);
        }
      }
      else {
        bufferUtil.mask(data, mask, data, 0, dataLength);
        try {
          this._socket.write(outputBuffer, 'binary', cb ? cbCaller : null);
          this._socket.write(data, 'binary', cb ? cbCaller : null);
        }
        catch (e) {
          if (typeof cb == 'function') cb(e);
          else this.emit('error', e);
        }
      }
    }
    else {
      outputBuffer[1] = secondByte;
      try {
        this._socket.write(outputBuffer, 'binary', cb ? cbCaller : null);
        this._socket.write(data, 'binary', cb ? cbCaller : null);
      }
      catch (e) {
        if (typeof cb == 'function') cb(e);
        else this.emit('error', e);
      }
    }
  }
  
  module.exports = Sender;
  
  function writeUInt16BE(value, offset) {
    this[offset] = (value & 0xff00)>>8;
    this[offset+1] = value & 0xff;
  }
  
  function writeUInt32BE(value, offset) {
    this[offset] = (value & 0xff000000)>>24;
    this[offset+1] = (value & 0xff0000)>>16;
    this[offset+2] = (value & 0xff00)>>8;
    this[offset+3] = value & 0xff;
  }
  
  function getArrayBuffer(array) {
    var l = array.byteLength
      , buffer = new Buffer(l);
    for (var i = 0; i < l; ++i) {
      buffer[i] = array[i];
    }
    return buffer;
  }
  
  function getRandomMask() {
    return new Buffer([
      ~~(Math.random() * 255),
      ~~(Math.random() * 255),
      ~~(Math.random() * 255),
      ~~(Math.random() * 255)
    ]);
  }
  