content: // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
  // for details. All rights reserved. Use of this source code is governed by a
  // BSD-style license that can be found in the LICENSE file.
  
  part of dart.io;
  
  /**
   * A high-level class for communicating securely over a TCP socket, using
   * TLS and SSL. The [SecureSocket] exposes both a [Stream] and an
   * [IOSink] interface, making it ideal for using together with
   * other [Stream]s.
   */
  abstract class SecureSocket implements Socket {
    external factory SecureSocket._(RawSecureSocket rawSocket);
  
    /**
     * Constructs a new secure client socket and connect it to the given
     * [host] on port [port]. The returned Future will complete with a
     * [SecureSocket] that is connected and ready for subscription.
     *
     * If [sendClientCertificate] is set to true, the socket will send a client
     * certificate if one is requested by the server.
     *
     * If [certificateName] is the nickname of a certificate in the certificate
     * database, that certificate will be sent.
     *
     * If [certificateName] is null, which is the usual use case, an
     * appropriate certificate will be searched for in the database and
     * sent automatically, based on what the server says it will accept.
     *
     * [onBadCertificate] is an optional handler for unverifiable certificates.
     * The handler receives the [X509Certificate], and can inspect it and
     * decide (or let the user decide) whether to accept
     * the connection or not.  The handler should return true
     * to continue the [SecureSocket] connection.
     */
    static Future<SecureSocket> connect(
        host,
        int port,
        {bool sendClientCertificate: false,
         String certificateName,
         bool onBadCertificate(X509Certificate certificate)}) {
      return RawSecureSocket.connect(host,
                                     port,
                                     sendClientCertificate: sendClientCertificate,
                                     certificateName: certificateName,
                                     onBadCertificate: onBadCertificate)
          .then((rawSocket) => new SecureSocket._(rawSocket));
    }
  
    /**
     * Takes an already connected [socket] and starts client side TLS
     * handshake to make the communication secure. When the returned
     * future completes the [SecureSocket] has completed the TLS
     * handshake. Using this function requires that the other end of the
     * connection is prepared for TLS handshake.
     *
     * If the [socket] already has a subscription, this subscription
     * will no longer receive and events. In most cases calling
     * `pause` on this subscription before starting TLS handshake is
     * the right thing to do.
     *
     * If the [host] argument is passed it will be used as the host name
     * for the TLS handshake. If [host] is not passed the host name from
     * the [socket] will be used. The [host] can be either a [String] or
     * an [InternetAddress].
     *
     * Calling this function will _not_ cause a DNS host lookup. If the
     * [host] passed is a [String] the [InternetAddress] for the
     * resulting [SecureSocket] will have the passed in [host] as its
     * host value and the internet address of the already connected
     * socket as its address value.
     *
     * See [connect] for more information on the arguments.
     *
     */
    static Future<SecureSocket> secure(
        Socket socket,
        {host,
         bool sendClientCertificate: false,
         String certificateName,
         bool onBadCertificate(X509Certificate certificate)}) {
      var completer = new Completer();
      (socket as dynamic)._detachRaw()
          .then((detachedRaw) {
            return RawSecureSocket.secure(
              detachedRaw[0],
              subscription: detachedRaw[1],
              host: host,
              sendClientCertificate: sendClientCertificate,
              onBadCertificate: onBadCertificate);
            })
          .then((raw) {
            completer.complete(new SecureSocket._(raw));
          });
      return completer.future;
    }
  
    /**
     * Takes an already connected [socket] and starts server side TLS
     * handshake to make the communication secure. When the returned
     * future completes the [SecureSocket] has completed the TLS
     * handshake. Using this function requires that the other end of the
     * connection is going to start the TLS handshake.
     *
     * If the [socket] already has a subscription, this subscription
     * will no longer receive and events. In most cases calling
     * [:pause:] on this subscription before starting TLS handshake is
     * the right thing to do.
     *
     * If some of the data of the TLS handshake has already been read
     * from the socket this data can be passed in the [bufferedData]
     * parameter. This data will be processed before any other data
     * available on the socket.
     *
     * See [SecureServerSocket.bind] for more information on the
     * arguments.
     *
     */
    static Future<SecureSocket> secureServer(
        Socket socket,
        String certificateName,
        {List<int> bufferedData,
         bool requestClientCertificate: false,
         bool requireClientCertificate: false}) {
      var completer = new Completer();
      (socket as dynamic)._detachRaw()
          .then((detachedRaw) {
            return RawSecureSocket.secureServer(
              detachedRaw[0],
              certificateName,
              subscription: detachedRaw[1],
              bufferedData: bufferedData,
              requestClientCertificate: requestClientCertificate,
              requireClientCertificate: requireClientCertificate);
            })
          .then((raw) {
            completer.complete(new SecureSocket._(raw));
          });
      return completer.future;
    }
  
    /**
     * Get the peer certificate for a connected SecureSocket.  If this
     * SecureSocket is the server end of a secure socket connection,
     * [peerCertificate] will return the client certificate, or null, if no
     * client certificate was received.  If it is the client end,
     * [peerCertificate] will return the server's certificate.
     */
    X509Certificate get peerCertificate;
  
    /**
     * Renegotiate an existing secure connection, renewing the session keys
     * and possibly changing the connection properties.
     *
     * This repeats the SSL or TLS handshake, with options that allow clearing
     * the session cache and requesting a client certificate.
     */
    void renegotiate({bool useSessionCache: true,
                      bool requestClientCertificate: false,
                      bool requireClientCertificate: false});
  
    /**
     * Initializes the NSS library. If [initialize] is not called, the library
     * is automatically initialized as if [initialize] were called with no
     * arguments. If [initialize] is called more than once, or called after
     * automatic initialization has happened (when a secure connection is made),
     * then a TlsException is thrown.
     *
     * The optional argument [database] is the path to a certificate database
     * directory containing root certificates for verifying certificate paths on
     * client connections, and server certificates to provide on server
     * connections. The argument [password] should be used when creating
     * secure server sockets, to allow the private key of the server
     * certificate to be fetched. If [useBuiltinRoots] is true (the default),
     * then a built-in set of root certificates for trusted certificate
     * authorities is merged with the certificates in the database.
     * The list of built-in root certificates, and documentation about this
     * default database, is available at
     * http://www.mozilla.org/projects/security/certs/included/ .
     *
     * If the [database] argument is omitted, then only the
     * builtin root certificates are used. If [useBuiltinRoots] is also false,
     * then no certificates are available.
     *
     * Examples:
     *   1) Use only the builtin root certificates:
     *     SecureSocket.initialize(); or
     *
     *   2) Use a specified database directory and the builtin roots:
     *     SecureSocket.initialize(database: 'path/to/my/database',
     *                             password: 'my_password');
     *
     *   3) Use a specified database directory, without builtin roots:
     *     SecureSocket.initialize(database: 'path/to/my/database',
     *                             password: 'my_password'.
     *                             useBuiltinRoots: false);
     *
     * The database should be an NSS certificate database directory
     * containing a cert9.db file, not a cert8.db file.  This version of
     * the database can be created using the NSS certutil tool with "sql:" in
     * front of the absolute path of the database directory, or setting the
     * environment variable [[NSS_DEFAULT_DB_TYPE]] to "sql".
     */
    external static void initialize({String database,
                                     String password,
                                     bool useBuiltinRoots: true});
  }
  
  
  /**
   * RawSecureSocket provides a secure (SSL or TLS) network connection.
   * Client connections to a server are provided by calling
   * RawSecureSocket.connect.  A secure server, created with
   * RawSecureServerSocket, also returns RawSecureSocket objects representing
   * the server end of a secure connection.
   * The certificate provided by the server is checked
   * using the certificate database provided in SecureSocket.initialize, and/or
   * the default built-in root certificates.
   */
  abstract class RawSecureSocket implements RawSocket {
    /**
     * Constructs a new secure client socket and connect it to the given
     * host on the given port. The returned Future is completed with the
     * RawSecureSocket when it is connected and ready for subscription.
     *
     * The certificate provided by the server is checked using the certificate
     * database provided in [SecureSocket.initialize], and/or the default built-in
     * root certificates. If [sendClientCertificate] is
     * set to true, the socket will send a client certificate if one is
     * requested by the server. If [certificateName] is the nickname of
     * a certificate in the certificate database, that certificate will be sent.
     * If [certificateName] is null, which is the usual use case, an
     * appropriate certificate will be searched for in the database and
     * sent automatically, based on what the server says it will accept.
     *
     * [onBadCertificate] is an optional handler for unverifiable certificates.
     * The handler receives the [X509Certificate], and can inspect it and
     * decide (or let the user decide) whether to accept
     * the connection or not.  The handler should return true
     * to continue the [RawSecureSocket] connection.
     */
    static Future<RawSecureSocket> connect(
        host,
        int port,
        {bool sendClientCertificate: false,
         String certificateName,
         bool onBadCertificate(X509Certificate certificate)}) {
      return  _RawSecureSocket.connect(
          host,
          port,
          certificateName,
          is_server: false,
          sendClientCertificate: sendClientCertificate,
          onBadCertificate: onBadCertificate);
    }
  
    /**
     * Takes an already connected [socket] and starts client side TLS
     * handshake to make the communication secure. When the returned
     * future completes the [RawSecureSocket] has completed the TLS
     * handshake. Using this function requires that the other end of the
     * connection is prepared for TLS handshake.
     *
     * If the [socket] already has a subscription, pass the existing
     * subscription in the [subscription] parameter. The secure socket
     * will take over the subscription and process any subsequent
     * events. In most cases calling `pause` on this subscription before
     * starting TLS handshake is the right thing to do.
     *
     * If the [host] argument is passed it will be used as the host name
     * for the TLS handshake. If [host] is not passed the host name from
     * the [socket] will be used. The [host] can be either a [String] or
     * an [InternetAddress].
     *
     * Calling this function will _not_ cause a DNS host lookup. If the
     * [host] passed is a [String] the [InternetAddress] for the
     * resulting [SecureSocket] will have this passed in [host] as its
     * host value and the internet address of the already connected
     * socket as its address value.
     *
     * See [connect] for more information on the arguments.
     *
     */
    static Future<RawSecureSocket> secure(
        RawSocket socket,
        {StreamSubscription subscription,
         host,
         bool sendClientCertificate: false,
         String certificateName,
         bool onBadCertificate(X509Certificate certificate)}) {
      socket.readEventsEnabled = false;
      socket.writeEventsEnabled = false;
      return  _RawSecureSocket.connect(
          host != null ? host : socket.address,
          socket.port,
          certificateName,
          is_server: false,
          socket: socket,
          subscription: subscription,
          sendClientCertificate: sendClientCertificate,
          onBadCertificate: onBadCertificate);
    }
  
    /**
     * Takes an already connected [socket] and starts server side TLS
     * handshake to make the communication secure. When the returned
     * future completes the [RawSecureSocket] has completed the TLS
     * handshake. Using this function requires that the other end of the
     * connection is going to start the TLS handshake.
     *
     * If the [socket] already has a subscription, pass the existing
     * subscription in the [subscription] parameter. The secure socket
     * will take over the subscription and process any subsequent
     * events.
     *
     * If some of the data of the TLS handshake has already been read
     * from the socket this data can be passed in the [bufferedData]
     * parameter. This data will be processed before any other data
     * available on the socket.
     *
     * See [RawSecureServerSocket.bind] for more information on the
     * arguments.
     *
     */
    static Future<RawSecureSocket> secureServer(
        RawSocket socket,
        String certificateName,
        {StreamSubscription subscription,
         List<int> bufferedData,
         bool requestClientCertificate: false,
         bool requireClientCertificate: false}) {
      socket.readEventsEnabled = false;
      socket.writeEventsEnabled = false;
      return _RawSecureSocket.connect(
          socket.address,
          socket.remotePort,
          certificateName,
          is_server: true,
          socket: socket,
          subscription: subscription,
          bufferedData: bufferedData,
          requestClientCertificate: requestClientCertificate,
          requireClientCertificate: requireClientCertificate);
    }
  
    /**
     * Renegotiate an existing secure connection, renewing the session keys
     * and possibly changing the connection properties.
     *
     * This repeats the SSL or TLS handshake, with options that allow clearing
     * the session cache and requesting a client certificate.
     */
    void renegotiate({bool useSessionCache: true,
                      bool requestClientCertificate: false,
                      bool requireClientCertificate: false});
  
    /**
     * Get the peer certificate for a connected RawSecureSocket.  If this
     * RawSecureSocket is the server end of a secure socket connection,
     * [peerCertificate] will return the client certificate, or null, if no
     * client certificate was received.  If it is the client end,
     * [peerCertificate] will return the server's certificate.
     */
    X509Certificate get peerCertificate;
  }
  
  
  /**
   * X509Certificate represents an SSL certificate, with accessors to
   * get the fields of the certificate.
   */
  class X509Certificate {
    X509Certificate(this.subject,
                    this.issuer,
                    this.startValidity,
                    this.endValidity);
    final String subject;
    final String issuer;
    final DateTime startValidity;
    final DateTime endValidity;
  }
  
  
  class _FilterStatus {
    bool progress = false;  // The filter read or wrote data to the buffers.
    bool readEmpty = true;  // The read buffers and decryption filter are empty.
    bool writeEmpty = true;  // The write buffers and encryption filter are empty.
    // These are set if a buffer changes state from empty or full.
    bool readPlaintextNoLongerEmpty = false;
    bool writePlaintextNoLongerFull = false;
    bool readEncryptedNoLongerFull = false;
    bool writeEncryptedNoLongerEmpty = false;
  
    _FilterStatus();
  }
  
  
  class _RawSecureSocket extends Stream<RawSocketEvent>
                         implements RawSecureSocket {
    // Status states
    static final int HANDSHAKE = 201;
    static final int CONNECTED = 202;
    static final int CLOSED = 203;
  
    // Buffer identifiers.
    // These must agree with those in the native C++ implementation.
    static final int READ_PLAINTEXT = 0;
    static final int WRITE_PLAINTEXT = 1;
    static final int READ_ENCRYPTED = 2;
    static final int WRITE_ENCRYPTED = 3;
    static final int NUM_BUFFERS = 4;
  
    // Is a buffer identifier for an encrypted buffer?
    static bool _isBufferEncrypted(int identifier) => identifier >= READ_ENCRYPTED;
  
    RawSocket _socket;
    final Completer<_RawSecureSocket> _handshakeComplete =
        new Completer<_RawSecureSocket>();
    StreamController<RawSocketEvent> _controller;
    Stream<RawSocketEvent> _stream;
    StreamSubscription<RawSocketEvent> _socketSubscription;
    List<int> _bufferedData;
    int _bufferedDataIndex = 0;
    final InternetAddress address;
    final bool is_server;
    final String certificateName;
    final bool requestClientCertificate;
    final bool requireClientCertificate;
    final bool sendClientCertificate;
    final Function onBadCertificate;
  
    var _status = HANDSHAKE;
    bool _writeEventsEnabled = true;
    bool _readEventsEnabled = true;
    int _pauseCount = 0;
    bool _pendingReadEvent = false;
    bool _socketClosedRead = false;  // The network socket is closed for reading.
    bool _socketClosedWrite = false;  // The network socket is closed for writing.
    bool _closedRead = false;  // The secure socket has fired an onClosed event.
    bool _closedWrite = false;  // The secure socket has been closed for writing.
    Completer _closeCompleter = new Completer();  // The network socket is gone.
    _FilterStatus _filterStatus = new _FilterStatus();
    bool _connectPending = true;
    bool _filterPending = false;
    bool _filterActive = false;
  
    _SecureFilter _secureFilter = new _SecureFilter();
    int _filterPointer;
  
    static Future<_RawSecureSocket> connect(
        host,
        int requestedPort,
        String certificateName,
        {bool is_server,
         RawSocket socket,
         StreamSubscription subscription,
         List<int> bufferedData,
         bool requestClientCertificate: false,
         bool requireClientCertificate: false,
         bool sendClientCertificate: false,
         bool onBadCertificate(X509Certificate certificate)}) {
      var future;
      _verifyFields(host, requestedPort, certificateName, is_server,
                   requestClientCertificate, requireClientCertificate,
                   sendClientCertificate, onBadCertificate);
      if (host is String) {
        if (socket != null) {
          future = new Future.value(
              (socket.address as dynamic)._cloneWithNewHost(host));
        } else {
          future = InternetAddress.lookup(host).then((addrs) => addrs.first);
        }
      } else {
        future = new Future.value(host);
      }
      return future.then((addr) {
       return new _RawSecureSocket(addr,
                                   requestedPort,
                                   certificateName,
                                   is_server,
                                   socket,
                                   subscription,
                                   bufferedData,
                                   requestClientCertificate,
                                   requireClientCertificate,
                                   sendClientCertificate,
                                   onBadCertificate)
           ._handshakeComplete.future;
      });
    }
  
    _RawSecureSocket(
        InternetAddress this.address,
        int requestedPort,
        String this.certificateName,
        bool this.is_server,
        RawSocket socket,
        StreamSubscription this._socketSubscription,
        List<int> this._bufferedData,
        bool this.requestClientCertificate,
        bool this.requireClientCertificate,
        bool this.sendClientCertificate,
        bool this.onBadCertificate(X509Certificate certificate)) {
      _controller = new StreamController<RawSocketEvent>(
          sync: true,
          onListen: _onSubscriptionStateChange,
          onPause: _onPauseStateChange,
          onResume: _onPauseStateChange,
          onCancel: _onSubscriptionStateChange);
      _stream = _controller.stream;
      // Throw an ArgumentError if any field is invalid.  After this, all
      // errors will be reported through the future or the stream.
      _secureFilter.init();
      _filterPointer = _secureFilter._pointer();
      _secureFilter.registerHandshakeCompleteCallback(
          _secureHandshakeCompleteHandler);
      if (onBadCertificate != null) {
        _secureFilter.registerBadCertificateCallback(_onBadCertificateWrapper);
      }
      var futureSocket;
      if (socket == null) {
        futureSocket = RawSocket.connect(address, requestedPort);
      } else {
        futureSocket = new Future.value(socket);
      }
      futureSocket.then((rawSocket) {
        _socket = rawSocket;
        _socket.readEventsEnabled = true;
        _socket.writeEventsEnabled = false;
        if (_socketSubscription == null) {
          // If a current subscription is provided use this otherwise
          // create a new one.
          _socketSubscription = _socket.listen(_eventDispatcher,
                                               onError: _reportError,
                                               onDone: _doneHandler);
        } else {
          _socketSubscription.onData(_eventDispatcher);
          _socketSubscription.onError(_reportError);
          _socketSubscription.onDone(_doneHandler);
        }
        _secureFilter.connect(address.host,
                              (address as dynamic)._sockaddr_storage,
                              port,
                              is_server,
                              certificateName,
                              requestClientCertificate ||
                                  requireClientCertificate,
                              requireClientCertificate,
                              sendClientCertificate);
        _secureHandshake();
      })
      .catchError(_reportError);
    }
  
    StreamSubscription listen(void onData(RawSocketEvent data),
                              {Function onError,
                               void onDone(),
                               bool cancelOnError}) {
      _sendWriteEvent();
      return _stream.listen(onData,
                            onError: onError,
                            onDone: onDone,
                            cancelOnError: cancelOnError);
    }
  
    static void _verifyFields(host,
                              int requestedPort,
                              String certificateName,
                              bool is_server,
                              bool requestClientCertificate,
                              bool requireClientCertificate,
                              bool sendClientCertificate,
                              Function onBadCertificate) {
      if (host is! String && host is! InternetAddress) {
        throw new ArgumentError("host is not a String or an InternetAddress");
      }
      if (requestedPort is! int) {
        throw new ArgumentError("requestedPort is not an int");
      }
      if (requestedPort < 0 || requestedPort > 65535) {
        throw new ArgumentError("requestedPort is not in the range 0..65535");
      }
      if (certificateName != null && certificateName is! String) {
        throw new ArgumentError("certificateName is not null or a String");
      }
      if (certificateName == null && is_server) {
        throw new ArgumentError("certificateName is null on a server");
      }
      if (requestClientCertificate is! bool) {
        throw new ArgumentError("requestClientCertificate is not a bool");
      }
      if (requireClientCertificate is! bool) {
        throw new ArgumentError("requireClientCertificate is not a bool");
      }
      if (sendClientCertificate is! bool) {
        throw new ArgumentError("sendClientCertificate is not a bool");
      }
      if (onBadCertificate != null && onBadCertificate is! Function) {
        throw new ArgumentError("onBadCertificate is not null or a Function");
      }
     }
  
    int get port => _socket.port;
  
    InternetAddress get remoteAddress => _socket.remoteAddress;
  
    int get remotePort => _socket.remotePort;
  
    int available() {
      if (_status != CONNECTED) return 0;
      return _secureFilter.buffers[READ_PLAINTEXT].length;
    }
  
    Future<RawSecureSocket> close() {
      shutdown(SocketDirection.BOTH);
      return _closeCompleter.future;
    }
  
    void _completeCloseCompleter([dummy]) {
      if (!_closeCompleter.isCompleted) _closeCompleter.complete(this);
    }
  
    void _close() {
      _closedWrite = true;
      _closedRead = true;
      if (_socket != null) {
        _socket.close().then(_completeCloseCompleter);
      } else {
        _completeCloseCompleter();
      }
      _socketClosedWrite = true;
      _socketClosedRead = true;
      if (!_filterActive && _secureFilter != null) {
        _secureFilter.destroy();
        _secureFilter = null;
      }
      if (_socketSubscription != null) {
        _socketSubscription.cancel();
      }
      _controller.close();
      _status = CLOSED;
    }
  
    void shutdown(SocketDirection direction) {
      if (direction == SocketDirection.SEND ||
          direction == SocketDirection.BOTH) {
        _closedWrite = true;
        if (_filterStatus.writeEmpty) {
          _socket.shutdown(SocketDirection.SEND);
          _socketClosedWrite = true;
          if (_closedRead) {
            _close();
          }
        }
      }
      if (direction == SocketDirection.RECEIVE ||
          direction == SocketDirection.BOTH) {
        _closedRead = true;
        _socketClosedRead = true;
        _socket.shutdown(SocketDirection.RECEIVE);
        if (_socketClosedWrite) {
          _close();
        }
      }
    }
  
    bool get writeEventsEnabled => _writeEventsEnabled;
  
    void set writeEventsEnabled(bool value) {
      _writeEventsEnabled = value;
      if (value) {
        Timer.run(() => _sendWriteEvent());
      }
    }
  
    bool get readEventsEnabled => _readEventsEnabled;
  
    void set readEventsEnabled(bool value) {
        _readEventsEnabled = value;
        _scheduleReadEvent();
    }
  
    List<int> read([int length]) {
      if (length != null && (length is! int || length < 0)) {
          throw new ArgumentError(
              "Invalid length parameter in SecureSocket.read (length: $length)");
      }
      if (_closedRead) {
        throw new SocketException("Reading from a closed socket");
      }
      if (_status != CONNECTED) {
        return null;
      }
      var result = _secureFilter.buffers[READ_PLAINTEXT].read(length);
      _scheduleFilter();
      return result;
    }
  
    // Write the data to the socket, and schedule the filter to encrypt it.
    int write(List<int> data, [int offset, int bytes]) {
      if (bytes != null && (bytes is! int || bytes < 0)) {
          throw new ArgumentError(
              "Invalid bytes parameter in SecureSocket.read (bytes: $bytes)");
      }
      if (offset != null && (offset is! int || offset < 0)) {
          throw new ArgumentError(
              "Invalid offset parameter in SecureSocket.read (offset: $offset)");
      }
      if (_closedWrite) {
        _controller.addError(new SocketException("Writing to a closed socket"));
        return 0;
      }
      if (_status != CONNECTED) return 0;
      if (offset == null) offset = 0;
      if (bytes == null) bytes = data.length - offset;
  
      int written =
          _secureFilter.buffers[WRITE_PLAINTEXT].write(data, offset, bytes);
      if (written > 0) {
        _filterStatus.writeEmpty = false;
      }
      _scheduleFilter();
      return written;
    }
  
    X509Certificate get peerCertificate => _secureFilter.peerCertificate;
  
    bool _onBadCertificateWrapper(X509Certificate certificate) {
      if (onBadCertificate == null) return false;
      var result = onBadCertificate(certificate);
      if (result is bool) return result;
      throw new ArgumentError(
          "onBadCertificate callback returned non-boolean $result");
    }
  
    bool setOption(SocketOption option, bool enabled) {
      if (_socket == null) return false;
      return _socket.setOption(option, enabled);
    }
  
    void _eventDispatcher(RawSocketEvent event) {
      try {
        if (event == RawSocketEvent.READ) {
          _readHandler();
        } else if (event == RawSocketEvent.WRITE) {
          _writeHandler();
        } else if (event == RawSocketEvent.READ_CLOSED) {
          _closeHandler();
        }
      } catch (e, stackTrace) {
        _reportError(e, stackTrace);
      }
    }
  
    void _readHandler() {
      _readSocket();
      _scheduleFilter();
    }
  
    void _writeHandler() {
      _writeSocket();
      _scheduleFilter();
    }
  
    void _doneHandler() {
      if (_filterStatus.readEmpty) {
        _close();
      }
    }
  
    void _reportError(e, [StackTrace stackTrace]) {
      if (_status == CLOSED) {
        return;
      } else if (_connectPending) {
        // _connectPending is true until the handshake has completed, and the
        // _handshakeComplete future returned from SecureSocket.connect has
        // completed.  Before this point, we must complete it with an error.
        _handshakeComplete.completeError(e, stackTrace);
      } else {
        _controller.addError(e, stackTrace);
      }
      _close();
    }
  
    void _closeHandler() {
      if  (_status == CONNECTED) {
        if (_closedRead) return;
        _socketClosedRead = true;
        if (_filterStatus.readEmpty) {
          _closedRead = true;
          _controller.add(RawSocketEvent.READ_CLOSED);
          if (_socketClosedWrite) {
            _close();
          }
        } else {
          _scheduleFilter();
        }
      } else if (_status == HANDSHAKE) {
        _socketClosedRead = true;
        if (_filterStatus.readEmpty) {
        _reportError(
            new HandshakeException('Connection terminated during handshake'),
            null);
        } else {
          _secureHandshake();
        }
      }
    }
  
    void _secureHandshake() {
      try {
        _secureFilter.handshake();
        _filterStatus.writeEmpty = false;
        _readSocket();
        _writeSocket();
        _scheduleFilter();
      } catch (e, stackTrace) {
        _reportError(e, stackTrace);
      }
    }
  
    void renegotiate({bool useSessionCache: true,
                      bool requestClientCertificate: false,
                      bool requireClientCertificate: false}) {
      if (_status != CONNECTED) {
        throw new HandshakeException(
            "Called renegotiate on a non-connected socket");
      }
      _secureFilter.renegotiate(useSessionCache,
                                requestClientCertificate,
                                requireClientCertificate);
      _status = HANDSHAKE;
      _filterStatus.writeEmpty = false;
      _scheduleFilter();
    }
  
    void _secureHandshakeCompleteHandler() {
      _status = CONNECTED;
      if (_connectPending) {
        _connectPending = false;
        // We don't want user code to run synchronously in this callback.
        Timer.run(() => _handshakeComplete.complete(this));
      }
    }
  
    void _onPauseStateChange() {
      if (_controller.isPaused) {
        _pauseCount++;
      } else {
        _pauseCount--;
        if (_pauseCount == 0) {
          _scheduleReadEvent();
          _sendWriteEvent();  // Can send event synchronously.
        }
      }
  
      if (!_socketClosedRead || !_socketClosedWrite) {
        if (_controller.isPaused) {
          _socketSubscription.pause();
        } else {
          _socketSubscription.resume();
        }
      }
    }
  
    void _onSubscriptionStateChange() {
      if (_controller.hasListener) {
        // TODO(ajohnsen): Do something here?
      }
    }
  
    void _scheduleFilter() {
      _filterPending = true;
      _tryFilter();
    }
  
    void _tryFilter() {
      if (_status == CLOSED) return;
      if (_filterPending && !_filterActive) {
        _filterActive = true;
        _filterPending = false;
        _pushAllFilterStages().then((status) {
          _filterStatus = status;
          _filterActive = false;
          if (_status == CLOSED) {
            _secureFilter.destroy();
            _secureFilter = null;
            return;
          }
          if (_filterStatus.writeEmpty && _closedWrite && !_socketClosedWrite) {
            // Checks for and handles all cases of partially closed sockets.
            shutdown(SocketDirection.SEND);
            if (_status == CLOSED) return;
          }
          if (_filterStatus.readEmpty && _socketClosedRead && !_closedRead) {
            if (_status == HANDSHAKE) {
              _secureFilter.handshake();
              if (_status == HANDSHAKE) {
                throw new HandshakeException(
                    'Connection terminated during handshake');
              }
            }
            _closeHandler();
          }
          if (_status == CLOSED) return;
          if (_filterStatus.progress) {
            _filterPending = true;
            if (_filterStatus.writePlaintextNoLongerFull) _sendWriteEvent();
            if (_filterStatus.readEncryptedNoLongerFull) _readSocket();
            if (_filterStatus.writeEncryptedNoLongerEmpty) _writeSocket();
            if (_filterStatus.readPlaintextNoLongerEmpty) _scheduleReadEvent();
            if (_status == HANDSHAKE) _secureHandshake();
          }
          _tryFilter();
        }).catchError(_reportError);
      }
    }
  
    List<int> _readSocketOrBufferedData(int bytes) {
      if (_bufferedData != null) {
        if (bytes > _bufferedData.length - _bufferedDataIndex) {
          bytes = _bufferedData.length - _bufferedDataIndex;
        }
        var result = _bufferedData.sublist(_bufferedDataIndex,
                                            _bufferedDataIndex + bytes);
        _bufferedDataIndex += bytes;
        if (_bufferedData.length == _bufferedDataIndex) {
          _bufferedData = null;
        }
        return result;
      } else if (!_socketClosedRead) {
        return _socket.read(bytes);
      } else {
        return null;
      }
    }
  
    void _readSocket() {
      if (_status == CLOSED) return;
      var buffer = _secureFilter.buffers[READ_ENCRYPTED];
      if (buffer.writeFromSource(_readSocketOrBufferedData) > 0) {
        _filterStatus.readEmpty = false;
      }
    }
  
    void _writeSocket() {
      if (_socketClosedWrite) return;
      var buffer = _secureFilter.buffers[WRITE_ENCRYPTED];
      if (buffer.readToSocket(_socket)) {  // Returns true if blocked
        _socket.writeEventsEnabled = true;
      }
    }
  
    // If a read event should be sent, add it to the controller.
    _scheduleReadEvent() {
      if (!_pendingReadEvent &&
          _readEventsEnabled &&
          _pauseCount == 0 &&
          _secureFilter != null &&
          !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) {
        _pendingReadEvent = true;
        Timer.run(_sendReadEvent);
      }
    }
  
    _sendReadEvent() {
      _pendingReadEvent = false;
      if (_readEventsEnabled &&
          _pauseCount == 0 &&
          _secureFilter != null &&
          !_secureFilter.buffers[READ_PLAINTEXT].isEmpty) {
        _controller.add(RawSocketEvent.READ);
        _scheduleReadEvent();
      }
    }
  
    // If a write event should be sent, add it to the controller.
    _sendWriteEvent() {
      if (!_closedWrite &&
          _writeEventsEnabled &&
          _pauseCount == 0 &&
          _secureFilter != null &&
          _secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
        _writeEventsEnabled = false;
        _controller.add(RawSocketEvent.WRITE);
      }
    }
  
    Future<_FilterStatus> _pushAllFilterStages() {
      bool wasInHandshake = _status != CONNECTED;
      List args = new List(2 + NUM_BUFFERS * 2);
      args[0] = _filterPointer;
      args[1] = wasInHandshake;
      var bufs = _secureFilter.buffers;
      for (var i = 0; i < NUM_BUFFERS; ++i) {
        args[2 * i + 2] = bufs[i].start;
        args[2 * i + 3] = bufs[i].end;
      }
  
      return _IOService.dispatch(_SSL_PROCESS_FILTER, args).then((response) {
        if (response.length == 2) {
          _reportError(new TlsException('${response[1]} error ${response[0]}'),
                       null);
        }
        int start(int index) => response[2 * index];
        int end(int index) => response[2 * index + 1];
  
        _FilterStatus status = new _FilterStatus();
        // Compute writeEmpty as "write plaintext buffer and write encrypted
        // buffer were empty when we started and are empty now".
        status.writeEmpty = bufs[WRITE_PLAINTEXT].isEmpty &&
            start(WRITE_ENCRYPTED) == end(WRITE_ENCRYPTED);
        // If we were in handshake when this started, _writeEmpty may be false
        // because the handshake wrote data after we checked.
        if (wasInHandshake) status.writeEmpty = false;
  
        // Compute readEmpty as "both read buffers were empty when we started
        // and are empty now".
        status.readEmpty = bufs[READ_ENCRYPTED].isEmpty &&
            start(READ_PLAINTEXT) == end(READ_PLAINTEXT);
  
        _ExternalBuffer buffer = bufs[WRITE_PLAINTEXT];
        int new_start = start(WRITE_PLAINTEXT);
        if (new_start != buffer.start) {
          status.progress = true;
          if (buffer.free == 0) {
            status.writePlaintextNoLongerFull = true;
          }
          buffer.start = new_start;
        }
        buffer = bufs[READ_ENCRYPTED];
        new_start = start(READ_ENCRYPTED);
        if (new_start != buffer.start) {
          status.progress = true;
          if (buffer.free == 0) {
            status.readEncryptedNoLongerFull = true;
          }
          buffer.start = new_start;
        }
        buffer = bufs[WRITE_ENCRYPTED];
        int new_end = end(WRITE_ENCRYPTED);
        if (new_end != buffer.end) {
          status.progress = true;
          if (buffer.length == 0) {
            status.writeEncryptedNoLongerEmpty = true;
          }
          buffer.end = new_end;
        }
        buffer = bufs[READ_PLAINTEXT];
        new_end = end(READ_PLAINTEXT);
        if (new_end != buffer.end) {
          status.progress = true;
          if (buffer.length == 0) {
            status.readPlaintextNoLongerEmpty = true;
          }
          buffer.end = new_end;
        }
        return status;
      });
    }
  }
  
  
  /**
   * A circular buffer backed by an external byte array.  Accessed from
   * both C++ and Dart code in an unsynchronized way, with one reading
   * and one writing.  All updates to start and end are done by Dart code.
   */
  class _ExternalBuffer {
    _ExternalBuffer(this.size) {
      start = size~/2;
      end = size~/2;
    }
  
    void advanceStart(int bytes) {
      assert(start > end || start + bytes <= end);
      start += bytes;
      if (start >= size) {
        start -= size;
        assert(start <= end);
        assert(start < size);
      }
    }
  
    void advanceEnd(int bytes) {
      assert(start <= end || start > end + bytes);
      end += bytes;
      if (end >= size) {
        end -= size;
        assert(end < start);
        assert(end < size);
      }
    }
  
    bool get isEmpty => end == start;
  
    int get length {
      if (start > end) return size + end - start;
      return end - start;
    }
  
    int get linearLength {
      if (start > end) return size - start;
      return end - start;
    }
  
    int get free {
      if (start > end) return start - end - 1;
      return size + start - end - 1;
    }
  
    int get linearFree {
      if (start > end) return start - end - 1;
      if (start == 0) return size - end - 1;
      return size - end;
    }
  
    List<int> read(int bytes) {
      if (bytes == null) {
        bytes = length;
      } else {
        bytes = min(bytes, length);
      }
      if (bytes == 0) return null;
      List<int> result = new Uint8List(bytes);
      int bytesRead = 0;
      // Loop over zero, one, or two linear data ranges.
      while (bytesRead < bytes) {
        int toRead = min(bytes, linearLength);
        result.setRange(bytesRead,
                        bytesRead + toRead,
                        data,
                        start);
        advanceStart(toRead);
        bytesRead += toRead;
      }
      return result;
    }
  
    int write(List<int> inputData, int offset, int bytes) {
      if (bytes > free) {
        bytes = free;
      }
      int written = 0;
      int toWrite = min(bytes, linearFree);
      // Loop over zero, one, or two linear data ranges.
      while (toWrite > 0) {
        data.setRange(end, end + toWrite, inputData, offset);
        advanceEnd(toWrite);
        offset += toWrite;
        written += toWrite;
        toWrite = min(bytes - written, linearFree);
      }
      return written;
    }
  
    int writeFromSource(List<int> getData(int requested)) {
      int written = 0;
      int toWrite = linearFree;
      // Loop over zero, one, or two linear data ranges.
      while (toWrite > 0) {
        // Source returns at most toWrite bytes, and it returns null when empty.
        var inputData = getData(toWrite);
        if (inputData == null) break;
        var len = inputData.length;
        data.setRange(end, end + len, inputData);
        advanceEnd(len);
        written += len;
        toWrite = linearFree;
      }
      return written;
    }
  
    bool readToSocket(RawSocket socket) {
      // Loop over zero, one, or two linear data ranges.
      while (true) {
        var toWrite = linearLength;
        if (toWrite == 0) return false;
        int bytes = socket.write(data, start, toWrite);
        advanceStart(bytes);
        if (bytes < toWrite) {
          // The socket has blocked while we have data to write.
          return true;
        }
      }
    }
  
    List data;  // This will be a ExternalByteArray, backed by C allocated data.
    int start;
    int end;
    final size;
  }
  
  
  abstract class _SecureFilter {
    external factory _SecureFilter();
  
    void connect(String hostName,
                 Uint8List addr,
                 int port,
                 bool is_server,
                 String certificateName,
                 bool requestClientCertificate,
                 bool requireClientCertificate,
                 bool sendClientCertificate);
    void destroy();
    void handshake();
    void rehandshake();
    void renegotiate(bool useSessionCache,
                     bool requestClientCertificate,
                     bool requireClientCertificate);
    void init();
    X509Certificate get peerCertificate;
    int processBuffer(int bufferIndex);
    void registerBadCertificateCallback(Function callback);
    void registerHandshakeCompleteCallback(Function handshakeCompleteHandler);
    int _pointer();
  
    List<_ExternalBuffer> get buffers;
  }
  
  /** A secure networking exception caused by a failure in the
   *  TLS/SSL protocol.
   */
  class TlsException implements IOException {
    final String type;
    final String message;
    final OSError osError;
  
    const TlsException([String message = "",
                        OSError osError = null])
       : this._("TlsException", message, osError);
  
    const TlsException._(String this.type,
                         String this.message,
                         OSError this.osError);
  
    String toString() {
      StringBuffer sb = new StringBuffer();
      sb.write(type);
      if (!message.isEmpty) {
        sb.write(": $message");
        if (osError != null) {
          sb.write(" ($osError)");
        }
      } else if (osError != null) {
        sb.write(": $osError");
      }
      return sb.toString();
    }
  }
  
  
  /**
   * An exception that happens in the handshake phase of establishing
   * a secure network connection.
   */
  class HandshakeException extends TlsException {
    const HandshakeException([String message = "",
                              OSError osError = null])
       : super._("HandshakeException", message, osError);
  }
  
  
  /**
   * An exception that happens in the handshake phase of establishing
   * a secure network connection, when looking up or verifying a
   * certificate.
   */
  class CertificateException extends TlsException {
    const CertificateException([String message = "",
                              OSError osError = null])
       : super._("CertificateException", message, osError);
  }
  