text: /**
   * Socket wrapping functions for TLS.
   *
   * @author Dave Longley
   *
   * Copyright (c) 2009-2012 Digital Bazaar, Inc.
   */
  var tls = require("./tls");
  
  /**
   * Wraps a forge.net socket with a TLS layer.
   *
   * @param options:
   *   sessionId: a session ID to reuse, null for a new connection if no session
   *     cache is provided or it is empty.
   *   caStore: an array of certificates to trust.
   *   sessionCache: a session cache to use.
   *   cipherSuites: an optional array of cipher suites to use, see
   *     tls.CipherSuites.
   *   socket: the socket to wrap.
   *   virtualHost: the virtual server name to use in a TLS SNI extension.
   *   verify: a handler used to custom verify certificates in the chain.
   *   getCertificate: an optional callback used to get a certificate.
   *   getPrivateKey: an optional callback used to get a private key.
   *   getSignature: an optional callback used to get a signature.
   *   deflate: function(inBytes) if provided, will deflate TLS records using
   *     the deflate algorithm if the server supports it.
   *   inflate: function(inBytes) if provided, will inflate TLS records using
   *     the deflate algorithm if the server supports it.
   *
   * @return the TLS-wrapped socket.
   */
  module.exports = function(options) {
    // get raw socket
    var socket = options.socket;
  
    // create TLS socket
    var tlsSocket = {
      id: socket.id,
      // set handlers
      connected: socket.connected || function(e) {},
      closed: socket.closed || function(e) {},
      data: socket.data || function(e) {},
      error: socket.error || function(e) {}
    };
  
    // create TLS connection
    var c = tls.createConnection({
      server: false,
      sessionId: options.sessionId || null,
      caStore: options.caStore || [],
      sessionCache: options.sessionCache || null,
      cipherSuites: options.cipherSuites || null,
      virtualHost: options.virtualHost,
      verify: options.verify,
      getCertificate: options.getCertificate,
      getPrivateKey: options.getPrivateKey,
      getSignature: options.getSignature,
      deflate: options.deflate,
      inflate: options.inflate,
      connected: function(c) {
        // first handshake complete, call handler
        if(c.handshakes === 1) {
          tlsSocket.connected({
            id: socket.id,
            type: 'connect',
            bytesAvailable: c.data.length()
          });
        }
      },
      tlsDataReady: function(c) {
        // send TLS data over socket
        return socket.send(c.tlsData.getBytes());
      },
      dataReady: function(c) {
        // indicate application data is ready
        tlsSocket.data({
          id: socket.id,
          type: 'socketData',
          bytesAvailable: c.data.length()
        });
      },
      closed: function(c) {
        // close socket
        socket.close();
      },
      error: function(c, e) {
        // send error, close socket
        tlsSocket.error({
          id: socket.id,
          type: 'tlsError',
          message: e.message,
          bytesAvailable: 0,
          error: e
        });
        socket.close();
      }
    });
  
    // handle doing handshake after connecting
    socket.connected = function(e) {
      c.handshake(options.sessionId);
    };
  
    // handle closing TLS connection
    socket.closed = function(e) {
      if(c.open && c.handshaking) {
        // error
        tlsSocket.error({
          id: socket.id,
          type: 'ioError',
          message: 'Connection closed during handshake.',
          bytesAvailable: 0
        });
      }
      c.close();
  
      // call socket handler
      tlsSocket.closed({
        id: socket.id,
        type: 'close',
        bytesAvailable: 0
      });
    };
  
    // handle error on socket
    socket.error = function(e) {
      // error
      tlsSocket.error({
        id: socket.id,
        type: e.type,
        message: e.message,
        bytesAvailable: 0
      });
      c.close();
    };
  
    // handle receiving raw TLS data from socket
    var _requiredBytes = 0;
    socket.data = function(e) {
      // drop data if connection not open
      if(!c.open) {
        socket.receive(e.bytesAvailable);
      } else {
        // only receive if there are enough bytes available to
        // process a record
        if(e.bytesAvailable >= _requiredBytes) {
          var count = Math.max(e.bytesAvailable, _requiredBytes);
          var data = socket.receive(count);
          if(data !== null) {
            _requiredBytes = c.process(data);
          }
        }
      }
    };
  
    /**
     * Destroys this socket.
     */
    tlsSocket.destroy = function() {
      socket.destroy();
    };
  
    /**
     * Sets this socket's TLS session cache. This should be called before
     * the socket is connected or after it is closed.
     *
     * The cache is an object mapping session IDs to internal opaque state.
     * An application might need to change the cache used by a particular
     * tlsSocket between connections if it accesses multiple TLS hosts.
     *
     * @param cache the session cache to use.
     */
    tlsSocket.setSessionCache = function(cache) {
      c.sessionCache = tls.createSessionCache(cache);
    };
  
    /**
     * Connects this socket.
     *
     * @param options:
     *           host: the host to connect to.
     *           port: the port to connect to.
     *           policyPort: the policy port to use (if non-default), 0 to
     *              use the flash default.
     *           policyUrl: the policy file URL to use (instead of port).
     */
    tlsSocket.connect = function(options) {
      socket.connect(options);
    };
  
    /**
     * Closes this socket.
     */
    tlsSocket.close = function() {
      c.close();
    };
  
    /**
     * Determines if the socket is connected or not.
     *
     * @return true if connected, false if not.
     */
    tlsSocket.isConnected = function() {
      return c.isConnected && socket.isConnected();
    };
  
    /**
     * Writes bytes to this socket.
     *
     * @param bytes the bytes (as a string) to write.
     *
     * @return true on success, false on failure.
     */
    tlsSocket.send = function(bytes) {
      return c.prepare(bytes);
    };
  
    /**
     * Reads bytes from this socket (non-blocking). Fewer than the number of
     * bytes requested may be read if enough bytes are not available.
     *
     * This method should be called from the data handler if there are enough
     * bytes available. To see how many bytes are available, check the
     * 'bytesAvailable' property on the event in the data handler or call the
     * bytesAvailable() function on the socket. If the browser is msie, then the
     * bytesAvailable() function should be used to avoid race conditions.
     * Otherwise, using the property on the data handler's event may be quicker.
     *
     * @param count the maximum number of bytes to read.
     *
     * @return the bytes read (as a string) or null on error.
     */
    tlsSocket.receive = function(count) {
      return c.data.getBytes(count);
    };
  
    /**
     * Gets the number of bytes available for receiving on the socket.
     *
     * @return the number of bytes available for receiving.
     */
    tlsSocket.bytesAvailable = function() {
      return c.data.length();
    };
  
    return tlsSocket;
  };
  