text: /*
   * Copyright (c) 2021, Oracle and/or its affiliates.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License, version 2.0, as
   * published by the Free Software Foundation.
   *
   * This program is also distributed with certain software (including
   * but not limited to OpenSSL) that is licensed under separate terms,
   * as designated in a particular file or component or in included license
   * documentation.  The authors of MySQL hereby grant you an
   * additional permission to link the program and your derivative works
   * with the separately licensed software that they have included with
   * MySQL.
   *
   * Without limiting anything contained in the foregoing, this file,
   * which is part of MySQL Connector/Node.js, is also subject to the
   * Universal FOSS Exception, version 1.0, a copy of which can be found at
   * http://oss.oracle.com/licenses/universal-foss-exception.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   * See the GNU General Public License, version 2.0, for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software Foundation, Inc.,
   * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
   */
  
  'use strict';
  
  const Client = require('../Protocol/Client');
  const Net = require('net');
  const TLS = require('tls');
  const authenticationManager = require('../Authentication/AuthenticationManager');
  const errors = require('../constants/errors');
  const logger = require('../logger');
  const multiHost = require('../topology/multi-host');
  const pkg = require('../package');
  const secureContext = require('../tls/secure-context');
  const srv = require('../topology/dns-srv');
  const system = require('../system');
  const util = require('util');
  const warnings = require('../constants/warnings');
  const { isValidBoolean, isValidInteger, isValidPlainObject } = require('../validator');
  
  /**
   * Connection factory.
   * @module Connection
   */
  
  /**
   * MySQL server endpoint details.
   * @typedef {Object} Endpoint
   * @prop {string} [host=localhost] - hostname or IP (v4 or v6) of a MySQL server instance
   * @prop {number} [port=33060] - X Plugin port on the MySQL server instance
   * @prop {number} [priority] - priority of an endpoint relative to the others (endpoints with higher priority are picked first)
   * @prop {string} [socket] - relative or absolute path of a local Unix socket file
   */
  
  /**
   * Connection TLS-specific properties.
   * @typedef {Object} TLS
   * @prop {boolean} [enabled=true] - enables or disables TLS
   * @prop {string} [ca] - path of a file containing a certificate authority chain used to verify the server certificate
   * @prop {string} [crl] - path of a file containing a certificate revocation list used, alongside a certificate authority, to verify the server certificate
   * @prop {string[]} [versions=TLSv1.2, TLSv1.3] - restrict the list of allowed TLS versions (TLSv1.2, TLSv1.3)
   * @prop {string[]} [ciphershuites] - list of ciphersuites to allow (IANA syntax)
   */
  
  /**
   * Connection configuration properties.
   * @typedef {Object} Properties
   * @prop {string} [auth] - name of the client-side authentication mechanism to use
   * @prop {number} [connectTimeout=10000] - maximum ammount of time (ms) to wait for a server connection to be opened
   * @prop {Object} [connectionAttributes={}] - key-value object containing names and values of session attributes
   * @prop {module:Connection~Endpoint[]} [endpoints=[]] - list of endpoints to connect to
   * @prop {string} [host=localhost] - hostname or IP (v4 or v6) of a MySQL server instance
   * @prop {string} [password] - password for the MySQL account (defaults to '')
   * @prop {number} [port=33060] - X Plugin port on the MySQL server instance
   * @prop {boolean} [resolveSrv=false] - enable or disable DNS SRV resolution
   * @prop {string} [schema] - default database to connect to (defaults to '')
   * @prop {string} [socket] - relative or absolute path of a local Unix socket file
   * @prop {boolean} [resolveSrv=false] - use the host to perform a DNS SRV lookup and obtain the list of endpoints
   * @prop {module:Connection~TLS} [tls] - TLS options
   * @prop {string} [user] - user of the MySQL account (defaults to '')
   */
  const VALID_OPTIONS = [
      'auth',
      'connectTimeout',
      'connectionAttributes',
      'dbPassword', // deprecated
      'dbUser', // deprecated
      'endpoints',
      'host',
      'password',
      'port',
      'resolveSrv',
      'schema',
      'socket',
      'ssl', // deprecated
      'sslOptions', // deprecated
      'tls',
      'user'
  ];
  
  /**
   * @private
   * @typedef {Object} DefaultSessionAttributes Default set of session
   * attributes created by the client when connecting to a MySQL server that
   * supports session attributes.
   * @prop {string} _pid - client process id
   * @prop {string} _platform - platform name of the processor architecture
   * @prop {string} _os - name and version of the operating system
   * @prop {string} _client_name - name of the client product
   * @prop {string} _client_version - version of the client product
   * @prop {string} _client_license - license name of the client product
   */
  const CLIENT_SESSION_ATTRIBUTES = {
      _pid: system.pid(),
      _platform: system.platform(),
      _os: system.brand(),
      _source_host: system.hostname(),
      _client_name: pkg.name(),
      _client_version: pkg.version(),
      _client_license: pkg.license()
  };
  
  /**
   * Stringifies all the values in a given object.
   * @private
   * @param {Object} obj
   * @returns {Object}
   */
  function stringifyValues (obj = {}) {
      let root = true;
  
      return JSON.stringify(obj, (k, v) => {
          // The replacer should skip the root node to avoid a stack overflow.
          if (root) {
              root = false;
              return v;
          }
  
          // If the value is undefined, in order to keep consistency between
          // connection strings and configuration objects, we coerce it to
          // an empty string. If the value is null (only possible with a
          // configuration object), we also coerce to an empty string in order
          // to avoid saving 'null'.
          if (typeof v === 'undefined' || v === null) {
              return '';
          }
  
          // If the value is already a string, we should not do anything.
          if (typeof v === 'string') {
              return v;
          }
  
          // If the value is neither an object nor an array, we return its
          // stringified version.
          if (typeof v !== 'object') {
              return JSON.stringify(v);
          }
  
          // Otherwise we need to recursively do the same for the values in "v".
          return stringifyValues(v);
      });
  }
  
  /**
   * @private
   * @typedef {Object} DeprecatedSSLOptions
   * @prop {string} [ca] - deprecated connection property for providing a path to a certificate authority file
   * @prop {string} [crl] - deprecated connection property for providing a path to a certificate revocation list file
   */
  
  /**
   * Toggle deprecation warnings for deprecated connection properties.
   * @private
   * @param {Object} params
   * @param {string} [params.dbPassword] - deprecate connection property for the MySQL account password
   * @param {string} [params.dbUser] - deprecated connection property for the MySQL account user
   * @param {boolean} [params.ssl] - deprecated connection property for enabling or disabling TLS
   * @param {DeprecatedSSLOptions} [params.dbUser] - deprecated connection property for additional SSL options
   * @returns {boolean}
   */
  function deprecate ({ dbPassword, dbUser, ssl, sslOptions }) {
      const log = logger('connection:options');
  
      // The "dbPassword" property is deprecated.
      if (typeof dbPassword !== 'undefined') {
          log.warning('dbPassword', warnings.MESSAGES.WARN_DEPRECATED_DB_PASSWORD, {
              type: warnings.TYPES.DEPRECATION,
              code: warnings.CODES.DEPRECATION
          });
      }
  
      // The "dbUser" property is deprecated.
      if (typeof dbUser !== 'undefined') {
          log.warning('dbUser', warnings.MESSAGES.WARN_DEPRECATED_DB_USER, {
              type: warnings.TYPES.DEPRECATION,
              code: warnings.CODES.DEPRECATION
          });
      }
  
      // The "ssl" and "sslOptions" properties are deprecated.
      if (typeof ssl !== 'undefined') {
          log.warning('ssl', warnings.MESSAGES.WARN_DEPRECATED_SSL_OPTION, {
              type: warnings.TYPES.DEPRECATION,
              code: warnings.CODES.DEPRECATION
          });
      }
  
      if (typeof sslOptions !== 'undefined') {
          log.warning('sslOptions', warnings.MESSAGES.WARN_DEPRECATED_SSL_ADDITIONAL_OPTIONS, {
              type: warnings.TYPES.DEPRECATION,
              code: warnings.CODES.DEPRECATION
          });
      }
  
      return true;
  }
  
  /**
   * @alias module:Connection
   * @param {Properties} - connection properties
   * @returns {module:Connection}
   */
  function Connection ({ auth, connectionAttributes = {}, connectTimeout = 10000, dbPassword, dbUser, endpoints = [], host = 'localhost', password = '', port = 33060, schema, socket, resolveSrv = false, ssl, sslOptions, tls = {}, user = '' } = {}) {
      // Internal connection state.
      const state = {
          // Contains the name of the authentication mechanism that is
          // effectively negotiated with the server.
          auth,
          // Indicates if the connection supports prepared statements (MySQL
          // 8.0.16 or higher).
          canPrepareStatements: true,
          // Contains the list of connection capabilities that were effectively
          // negotiated with the server.
          capabilities: {},
          // Tracks an internal X Protocol client instance.
          client: null,
          // We keep a list of available and unavailable endpoints for
          // connection failover.
          endpoints: {
              // If there is a list of endpoints, we should use it.
              // If not, it should only include an endpoint with the details
              // specified by the "host", "port" and/or "socket" properties or
              // by the first and only element in that list.
              // When a connection is first opened, all endpoints should be
              // available. This can change if a socket cannot be created for
              // a given endpoint.
              available: endpoints.length ? endpoints : [{ host, port, socket }],
              unavailable: []
          },
          // Indicates the connection is being closed.
          isClosing: false,
          // Indicates if the connection established with the server
          // is using TLS.
          isSecure: false,
          // Indicates if the connection should be retried to the same endpoint.
          retry: false,
          // Indicates the number of milliseconds to wait before retrying a
          // specific endpoint.
          retryAfter: 20000,
          // Contains the connection id assigned by the server.
          serverId: null,
          // Contains the list of statements that were prepared and exist in the
          // scope of the associated server session.
          statements: [],
          // Merges any contents of the deprecated "ssl" and "sslOptions"
          // properties with the content of the "tls" property.
          // For tls.enable = true, we need to ensure that ssl !== false.
          // Additional options are merged with precedence for those defined
          // using the "tls" property, except for "ciphersuites" and "versions"
          // which we want to override given the validation constraints.
          tls: Object.assign({}, { enabled: ssl === false ? ssl : true }, sslOptions, tls),
          // Tracks the list of capabilities that the server does not know.
          unknownCapabilities: []
      };
  
      return {
          /**
           * Adds a set of connection capabilities to the existing ones.
           * @private
           * @function
           * @name module:Connection#addCapabilities
           * @param {Object} capabilities - set of capabilities
           * @returns {module:Connection} The connection instance
           */
          addCapabilities (capabilities) {
              state.capabilities = Object.assign({}, state.capabilities, capabilities);
  
              return this;
          },
  
          /**
           * Checks if the connection setup allows to retry authentication.
           * @private
           * @function
           * @name module:Connection:allowsAuthenticationRetry
           * @returns {boolean}
           */
          allowsAuthenticationRetry () {
              // If the application provides its own authentication mechanism,
              // there is no reason to retry.
              if (this.hasCustomAuthenticationMechanism()) {
                  return false;
              }
  
              // If the connection is secure, it means the authentication
              // failed because the credentials did not match, so we should
              // not allow a retry.
              if (state.endpoints.available[0].socket || state.tls.enabled) {
                  return false;
              }
  
              // Which leaves us with checking if the server supports or not
              // the fallback authentication mechanism.
              return this.allowsAuthenticationWith('SHA256_MEMORY');
          },
  
          /**
           * Checks if a given authentication mechanism can be used with the
           * connection.
           * @private
           * @function
           * @name module:Connection#allowsAuthenticationWith
           * @param {string} mechanism - name of the authentication mechanism
           * @returns {boolean}
           */
          allowsAuthenticationWith (mechanism) {
              // This is a workaround because on MySQL 5.7.x, calling
              // Mysqlx.Connection::CapabilitiesGet over a unix socket does not
              // return `PLAIN` while is in fact supported.
              const serverSupportedMechanisms = (state.capabilities['authentication.mechanisms'] || []).concat('PLAIN');
  
              return serverSupportedMechanisms.indexOf(mechanism) > -1;
          },
  
          /**
           * Negotiates the proper authentication mechanism for the connection user.
           * @private
           * @function
           * @name module:Connection#authenticate
           * @returns {module:Connection} The connection instance
           */
          authenticate () {
              // Try the custom authentication mechanism, if it is provided,
              // PLAIN, if the connection is secure, or MYSQL41, if not.
              const mechanism = this.getAuth();
  
              if (!this.allowsAuthenticationWith(mechanism)) {
                  const message = util.format(errors.MESSAGES.ER_DEVAPI_AUTH_UNSUPPORTED_SERVER, mechanism);
                  const error = new Error(message);
                  error.info = { code: errors.ER_ACCESS_DENIED_ERROR, msg: message };
  
                  return Promise.reject(error);
              }
  
              return this.authenticateWith(mechanism)
                  .catch(err => {
                      // If the setup does not allow to retry the authentication
                      // or if the error is not related to authentication we
                      // should report it.
                      if (!this.allowsAuthenticationRetry() || (err.info && err.info.code !== errors.ER_ACCESS_DENIED_ERROR)) {
                          throw err;
                      }
  
                      // Otherwise we should perform the fallback strategy and
                      // try with SHA256_MEMORY.
                      return this.authenticateWith('SHA256_MEMORY')
                          .catch(err => {
                              // If it is not an authentication error, we should bubble that
                              // error instead.
                              if (err.info && err.info.code !== errors.ER_ACCESS_DENIED_ERROR) {
                                  throw err;
                              }
  
                              // Otherwise, we improve the existing error message.
                              err.message = err.info.msg = errors.MESSAGES.ER_DEVAPI_AUTH_MORE_INFO;
  
                              throw err;
                          });
                  });
          },
  
          /**
           * Authenticates the connection user with a given mechanism.
           * @private
           * @function
           * @name module:Connection#authenticateWith
           * @param {string} mechanism - name of the authentication mechanism
           * @returns {Promise<module:Connection>} A Promise that resolves to the
           * connection instance.
           */
          authenticateWith (mechanism) {
              const plugin = authenticationManager.getPlugin(mechanism);
              const theUser = this.getUser();
              // We don't expose a getPassword() method because, the connection
              // is publicly available with session._getConnection()
              const thePassword = password || dbPassword || '';
  
              return plugin({ password: thePassword, schema, user: theUser }).run(state.client)
                  .then(session => {
                      state.auth = mechanism;
                      state.serverId = session.connectionId;
  
                      return this;
                  });
          },
  
          /**
           * Retrieves the server-side connection capabilities that have been
           * effectively negotiated.
           * @private
           * @function
           * @name module:Connection#capabilitiesGet
           * @returns {Promise}
           */
          capabilitiesGet () {
              return state.client.capabilitiesGet();
          },
  
          /**
           * Negotiates a set of connection capabilities with the server
           * (X Plugin).
           * @private
           * @function
           * @name module:Connection#capabilitiesSet
           * @returns {Promise}
           */
          capabilitiesSet () {
              // TODO(Rui): any additional capability, such as compression, can
              // be negotiated in this pipeline.
              const capabilities = {};
  
              // If TLS is enabled and the connection is not using a local Unix socket,
              // we need to enable TLS in the X Plugin.
              if (state.tls.enabled === true && !state.endpoints.available[0].socket) {
                  // The X Protocol capability name is "tls".
                  capabilities.tls = true;
              }
  
              // If connection attributes are to be sent, we need to merge the
              // default client attributes. Additionally, if the server does
              // not support session attributes, the connection is re-tried and
              // we should disable them in the second attempt.
              if (connectionAttributes !== false && state.unknownCapabilities.indexOf('session_connect_attrs') === -1) {
                  const attributes = Object.assign({}, CLIENT_SESSION_ATTRIBUTES, connectionAttributes);
                  // The X Plugin requires all connection attributes to be
                  // encoded as strings. The client attributes are already
                  // expected to be strings. However, custom application
                  // attributes that are not strings need to be coerced.
                  // If they are not able to be coerced (e.g. null or
                  // undefined), we should propagate the error reported by the
                  // plugin.
                  // The X Protocol capability name is "session_connect_attrs".
                  capabilities.session_connect_attrs = JSON.parse(stringifyValues(attributes));
              }
  
              return state.client.capabilitiesSet(capabilities)
                  .then(() => {
                      // Must return the client capabilities for further
                      // processing.
                      return capabilities;
                  })
                  .catch(err => {
                      // When TLS is not enabled in the server, we want
                      // to report back a custom message.
                      if (err.info && err.info.code === errors.ER_X_CAPABILITIES_PREPARE_FAILED) {
                          // By TLS not being enabled, it means that the X
                          // Plugin will report an error stating that the "tls"
                          // capability failed. This is a generic error that can
                          // mean any other capability is invalid, so we need to
                          // ensure we are dealing with TLS. Currently the only
                          // way to retrieve the capability name is to parse the
                          // error message.
                          const failedCapability = err.message.match(/Capability prepare failed for '([^']+)'.*/)[1];
                          // If the failure is not related to TLS, we should
                          // report the error as is.
  
                          if (failedCapability !== 'tls') {
                              throw err;
                          }
  
                          // Otherwise, we want to report a custom message.
                          const message = errors.MESSAGES.ER_DEVAPI_NO_SERVER_TLS;
                          err.message = message;
                          err.info.msg = message;
  
                          throw err;
                      }
  
                      // When a new capability is introduced, it will be unknown
                      // to older server versions. In that case, the server will
                      // report an error and will close the connection. However,
                      // since there is no way to ensure the capability is
                      // supported beforehand, we have to try and send it anyway
                      // asking for forgiveness instead of permission. Thus, in
                      // order to make it seamless to the application, we need
                      // to destroy the network socket and re-create the
                      // connection without the unknown capabilities.
                      if (!err.info || err.info.code !== errors.ER_X_CAPABILITY_NOT_FOUND) {
                          throw err;
                      }
  
                      // By this point, the server has sent back an
                      // ER_X_CAPABILITY_NOT_FOUND error and has closed the
                      // connection.
                      // So, the first thing we need to do is to track the
                      // unknown capability. Currently the only way to retrieve
                      // the capability name is to parse the error message.
                      const unknownCapability = err.message.match(/Capability '([^']+)'.*/)[1];
                      state.unknownCapabilities.push(unknownCapability);
  
                      // And we need to ensure that we re-create a connection to
                      // the same endpoint. Since we are now tracking unknown
                      // capabilities, any new connection will not try to
                      // negotiate those.
                      state.retry = true;
  
                      const socket = state.client.getConnection();
                      socket.destroy(err);
                  });
          },
  
          /**
           * Closes the X Protocol connection and the underlying connection
           * socket.
           * This method is overriden by PoolConnection
           * @private
           * @function
           * @name module:Connection#close
           * @returns {Promise}
           */
          close () {
              return this.destroy();
          },
  
          /**
           * Creates a network socket to the most appropriate MySQL server
           * endpoint.
           * @private
           * @function
           * @name module:Connection#connect
           * @returns {Promise<module:Connection>}
           */
          connect () {
              return new Promise((resolve, reject) => {
                  // We want to connect to the first endpoint that is available.
                  const endpoint = state.endpoints.available[0];
                  // In the case of multi-host connections, we want sane
                  // defaults for each endpoint in the list, in order to
                  // require less verbosity from the user.
                  const nodeSocket = Net.connect({ host: endpoint.host || 'localhost', port: endpoint.port || 33060, path: endpoint.socket });
  
                  nodeSocket.setTimeout(connectTimeout);
  
                  // Actions to perform after a connection is successfully
                  // established (including server session).
                  const postConnect = connection => {
                      // We need to "remove" the timeout because we do not want
                      // the event to be triggered after the connection is
                      // effectively established.
                      // For now, we consider "connectTimeout" to be the maximum
                      // time it can take for a client socket (TLS or not) to
                      // successfully connect to the server.
                      nodeSocket.setTimeout(0);
                      return resolve(connection);
                  };
  
                  // Actions to perform when there is an error in the connection
                  // stage.
                  const postDisconnect = err => {
                      // We should cleanup the connection state.
                      this.reset();
  
                      // If there isn't an error, it means the socket has been
                      // closed by the server and the client was expecting it.
                      // There's nothing to do, the workflow is just finished
                      // by this point.
                      if (!err) {
                          return resolve();
                      }
  
                      // If there is an error, it means the socket has been
                      // closed with an error and we need to report it back
                      // to the application.
                      return reject(err);
                  };
  
                  // For now, we consider every error to happen at the
                  // connection stage, to be fatal, and thus, we should stop and
                  // close/destroy the connection.
                  nodeSocket.once('ready', () => {
                      // We can start creating the client instance, which will
                      // hold the given connection. This is important because
                      // we do not need to keep the raw socket available
                      // everywhere but we still want to decouple the TLS logic.
                      state.client = new Client(nodeSocket);
  
                      // We can now start the process of creating a server-side
                      // X Protocol session.
                      return this.start().then(postConnect).catch(err => nodeSocket.destroy(err));
                  });
  
                  nodeSocket.once('error', err => {
                      // The "close" event will automatically be triggered.
                      state.error = err;
                  });
  
                  nodeSocket.once('timeout', () => {
                      const error = new Error();
                      error.name = 'ETIMEDOUT';
  
                      // The error message depends on whether the connection is
                      // multi-host or not.
                      if (!this.hasMultipleEndpoints()) {
                          error.message = util.format(errors.MESSAGES.ER_DEVAPI_CONNECTION_TIMEOUT, connectTimeout);
                      } else {
                          error.message = util.format(errors.MESSAGES.ER_DEVAPI_MULTI_HOST_CONNECTION_TIMEOUT, connectTimeout);
                      }
  
                      // The connection must be manually closed.
                      // https://nodejs.org/docs/latest/api/net.html#net_event_timeout
                      nodeSocket.destroy(error);
                  });
  
                  nodeSocket.on('data', data => {
                      state.client.handleNetworkFragment(data);
                  });
  
                  nodeSocket.once('close', hasError => {
                      // When the endpoint becomes unvailable in the middle of
                      // some work load, we should delegate error handling to
                      // the specific worker.
                      if (!hasError && this.isOpen() && this.isActive()) {
                          // We are already past the connection stage.
                          return state.client.handleServerClose();
                      }
  
                      // When a fatal error happens in the server, it will close
                      // the connection, but since it is not a network error,
                      // "hasError" will be "false", so we need to account for
                      // that cenario. In that case, we can check if the
                      // connection is already active or not.
                      if (!hasError && this.isOpen()) {
                          // It means the connection was legitimately closed.
                          return postDisconnect();
                      }
  
                      // If we cannot retry, the current connection should
                      // become unavailable. One reason to retry the current
                      // connection is if the server is available but refused
                      // the connection with a non-fatal error (such as when
                      // a capability is not known).
                      if (!state.retry) {
                          // When a connection becomes unavailable, it should
                          // include the timestamp of when it was last tried.
                          const unavailable = Object.assign({}, state.endpoints.available.shift(), {
                              unavailableAt: Date.now()
                          });
  
                          state.endpoints.unavailable.push(unavailable);
                      }
  
                      // If we are retrying now, we should prevent duplicate
                      // retries.
                      state.retry = false;
  
                      // By this point, there should be an error available in
                      // "state.error", which is updated for each error event in
                      // the socket (but also includes other kinds of errors).
                      const error = this.getError();
  
                      // We want to know if the the timeout has been reached for
                      // all the endpoints, or if there was a different issue.
                      if (!this.hasMoreEndpointsAvailable() && (!this.hasMultipleEndpoints() || error.name === 'ETIMEDOUT')) {
                          return postDisconnect(error);
                      }
  
                      // If we are in a multi-host setup and there are no more
                      // endpoints available, we want to raise a custom error
                      // as well.
                      if (!this.hasMoreEndpointsAvailable() && this.hasMultipleEndpoints()) {
                          error.name = 'ENOMOREHOSTS';
                          error.errno = errors.ER_DEVAPI_MULTI_HOST_CONNECTION_FAILED;
                          error.message = errors.MESSAGES.ER_DEVAPI_MULTI_HOST_CONNECTION_FAILED;
  
                          return postDisconnect(error);
                      }
  
                      // By this point, the list of available and unavailable
                      // endpoints is already up-to-date and if there are more
                      // endpoints available, we should try to connect to the
                      // next one in the list.
                      this.connect().then(postConnect).catch(postDisconnect);
                  });
              });
          },
  
          /**
           * Asks the server to gracefully close the underlying X Protocol
           * connection.
           * @private
           * @function
           * @name module:Connection#destroy
           * @returns {Promise}
           */
          destroy () {
              // This operation should be idempotent because there is a chance
              // that the connection might already been closed by the server
              // we should check first.
              if (!this.isOpen() || state.isClosing) {
                  return Promise.resolve();
              }
  
              const socket = state.client.getConnection();
  
              // Again, the operation should be idempotent, so, if the socket
              // reference has already been destroyed, there is nothing to do.
              if (socket.destroyed) {
                  return Promise.resolve();
              }
  
              // Eventually, the socket is destroyed and a 'close' event is
              // emmitted. This is here just to make sure the operation does
              // not fail if the connection is, for some weird reason, manually
              // closed again by the application.
              state.isClosing = true;
  
              // The method is shared between this and "PoolConnection".
              // We cannot re-use "close()" because it is overrided by "PoolConnection".
              return state.client.connectionClose()
                  .then(() => {
                      // The server closes the socket on their end so we also
                      // need to destroy our reference.
                      // This will trigger a 'close' event whose handler is responsible
                      // for further cleanup.
                      socket.destroy();
  
                      // The connection has been safely closed by now.
                      state.isClosing = false;
                  });
          },
  
          /**
           * Enables TLS on the underlying network socket.
           * @private
           * @function
           * @name module:Connection#enableTLS
           * @returns {Promise}
           */
          enableTLS () {
              return new Promise(resolve => {
                  const secureContextOptions = Object.assign({}, state.tls);
                  // We already know TLS should be enabled.
                  delete secureContextOptions.enabled;
                  // We need to create a secure socket by providing the existing
                  // socket and the proper security context.
                  const nodeSocket = TLS.connect(Object.assign({}, { socket: state.client.getConnection() }, secureContext.create(secureContextOptions)));
  
                  nodeSocket.once('secureConnect', () => {
                      state.isSecure = true;
                      // Once a TLS session is established, we need to
                      // update the socket reference.
                      state.client.setConnection(nodeSocket);
                      // Then we are done and can move along.
                      resolve();
                  });
  
                  // We need to re-attach the event listener in order to
                  // be able to handle OpenSSL errors.
                  nodeSocket.once('error', err => {
                      state.error = err;
                  });
  
                  // The stream is paused when a secure connection is
                  // established in the socket, so we need to resume the
                  // flow. The handler should be the same.
                  nodeSocket.on('data', data => {
                      state.client.handleNetworkFragment(data);
                  });
              });
          },
  
          /**
           * Checks if there is any ongoing workload in the connection.
           * @private
           * @function
           * @name module:Connection#isActive
           * @returns {boolean}
           */
          isActive () {
              return !!state.client && state.client.isRunning();
          },
  
          /**
           * Checks of the connection is being closed.
           * @private
           * @function module:Connection#isClosing
           * @returns {boolean}
           */
          isClosing () {
              return state.isClosing;
          },
  
          /**
           * Checks if the connection was created by a pool, which is never the
           * case for this API.
           * This method is overriden by {@link module:PoolConnection|PoolConnection}.
           * @private
           * @function
           * @name module:Connection#isFromPool
           * @returns {boolean} Returns false.
           */
          isFromPool () {
              return false;
          },
  
          /**
           * Checks if the connection is idle, which is never the case for this
           * API.
           * This method is overriden by {@link module:PoolConnection|PoolConnection}.
           * @private
           * @function
           * @name module:Connection#isIdle
           * @returns {boolean} Returns false.
           */
          isIdle () {
              // Standalone connections never become idle on the client side.
              return false;
          },
  
          /**
           * Checks if a server-side X Protocol connecion has been sucessfuly
           * established.
           * @private
           * @function
           * @name module:Connection#isOpen
           * @returns {boolean}
           */
          isOpen () {
              return state.client !== null && state.serverId !== null;
          },
  
          /**
           * Checks if we are in a middle of a connection retry.
           * @private
           * @function
           * @name module:Connection#isReconnecting
           * @returns {boolean}
           */
          isReconnecting () {
              return state.retry;
          },
  
          /**
           * Checks if the connection is using TLS.
           * @private
           * @function
           * @name module:Connection#isSecure
           * @returns {boolean}
           */
          isSecure () {
              return state.isSecure;
          },
  
          /**
           * Retrieves the name of the authentication mechanism that was
           * negotiated with the server.
           * @private
           * @function
           * @name module:Connection#getAuth
           * @returns {string}
           */
          getAuth () {
              // If an authentication mechanism is specified, we should try to
              // use it.
              if (state.auth) {
                  return state.auth;
              }
  
              // If one is not specified and the connection is not secure, we
              // should use a mechanism that works with most widespread plugin:
              // "mysql_native_password".
              if (!state.endpoints.available[0].socket && !state.tls.enabled) {
                  return 'MYSQL41';
              }
  
              // If the connection is secure, we should opt for the fastest
              // alternative which is sending credentials as clear text.
              return 'PLAIN';
          },
  
          /**
           * Retrieves the underlying X Protocol client instance.
           * @private
           * @function
           * @name module:Connection#getClient
           * @returns {Client}
           */
          getClient () {
              return state.client;
          },
  
          /**
           * Retrieves any error generated by the connection.
           * @private
           * @function
           * @name module:Connection#getError
           * @returns {Error}
           */
          getError () {
              // It does not make sense to have a default error value as part of
              // the connection state.
              return state.error || new Error(errors.MESSAGES.ER_DEVAPI_CONNECTION_CLOSED);
          },
  
          /**
           * Retrieves the list of client-side ids associated to server-side
           * prepared statements created in the scope of the underlying X
           * Protocol session.
           * @private
           * @function
           * @name module:Connection#getPreparedStatements
           * @returns {number[]}
           */
          getPreparedStatements () {
              return state.statements;
          },
  
          /**
           * Toggles the a flag to indicate the connection does not support
           * server-side prepared statements.
           * @private
           * @function
           * @name module:Connection#disablePreparedStatements
           * @returns {module:Connection}
           */
          disablePreparedStatements () {
              state.canPrepareStatements = false;
  
              return this;
          },
  
          /**
           * Removes a deallocated prepared statement for the list of statements
           * associated to the X Protocol session.
           * @private
           * @function
           * @name module:Connection#removePreparedStatement
           * @param {number} id - the client-side prepared statement id
           * @returns {module:Connection}
           */
          removePreparedStatement (id) {
              state.statements[id - 1] = undefined;
  
              return this;
          },
  
          /**
           * Retrieves the hostname or IP (v4 or v6) address of the machine
           * where the MySQL server is hosted.
           * @private
           * @function
           * @name module:Connection#getServerHostname
           * @returns {string}
           */
          getServerHostname () {
              const endpoint = state.endpoints.available[0];
  
              // Local socket file paths have precedence over host:port combos
              // in the core Node.js APIs used to create a network socket.
              // So, we want to make it clear that a Unix socket is being used
              // in this case.
              if (endpoint.socket) {
                  return;
              }
  
              return endpoint.host;
          },
  
          /**
           * Retrieves the server-side connection id.
           * @private
           * @function
           * @name module:Connection#getServerId
           * @returns {number}
           */
          getServerId () {
              return state.serverId;
          },
  
          /**
           * Retrieves the port number where the server is listening for
           * connections.
           * @private
           * @function
           * @name module:Connection#getServerPort
           * @returns {number}
           */
          getServerPort () {
              const endpoint = state.endpoints.available[0];
  
              // Local socket file paths have precedence over host:port combos
              // in the core Node.js APIs used to create a network socket.
              // So, we want to make it clear that a Unix socket is being used
              // in this case.
              if (endpoint.socket) {
                  return;
              }
  
              return endpoint.port;
          },
  
          /**
           * Retrieves the path to the local Unix socket file used for
           * connecting to the server.
           * @private
           * @function
           * @name module:Connection#getServerSocketPath
           * @returns {string}
           */
          getServerSocketPath () {
              return state.endpoints.available[0].socket;
          },
  
          /**
           * Retrieves the name of the default schema associated to the
           * connection.
           * @private
           * @function
           * @name module:Connection#getSchemaName
           * @returns {string}
           */
          getSchemaName () {
              return schema;
          },
  
          /**
           * Retrieves the list of capabilities that are not known by the server.
           * @private
           * @function
           * @name module:Connection#getUnknownCapabilities
           * @returns {string[]}
           */
          getUnknownCapabilities () {
              return state.unknownCapabilities;
          },
  
          /**
           * Retrieves the MySQL account user associated to the connection.
           * @private
           * @function
           * @name module:Connection#getUser
           * @returns {string}
           */
          getUser () {
              // TODO(Rui): "dbUser" is deprecated.
              return user || dbUser || '';
          },
  
          /**
           * Checks if the connection is using a custom authentication mechanism
           * provided by the application.
           * @private
           * @function
           * @name module:Connection#hasCustomAuthenticationMechanism
           * @returns {boolean}
           */
          hasCustomAuthenticationMechanism () {
              return !!auth;
          },
  
          /**
           * Checks if there are endpoints available.
           * @private
           * @function
           * @name module:Connection#hasMoreEndpointsAvailable
           * @returns {boolean}
           */
          hasMoreEndpointsAvailable () {
              // Make sure the list of available endpoints is up-to-date.
              this.update();
              // If there are available endpoints, we can perform a failover.
              return state.endpoints.available.length > 0;
          },
  
          /**
           * Checks if the connection was configured with multiple endpoints.
           * @private
           * @function
           * @name module:Connection#hasMultipleEndpoints
           * @returns {boolean}
           */
          hasMultipleEndpoints () {
              return state.endpoints.available.length + state.endpoints.unavailable.length > 1;
          },
  
          /**
           * Creates a new connection to a MySQL endpoint.
           * @private
           * @function
           * @name module:Connection#open
           * @returns {Promise<module:Connection>}
           */
          open () {
              // Make sure the list of available endpoints is up-to-date.
              this.update();
  
              // If "resolveSrv" is disabled, it means we already have an
              // ordered list of endpoints and we can try to connect to the
              // first one.
              if (!resolveSrv) {
                  // We sort the list of endpoints (one or more) according to
                  // the set of multi-host rules.
                  state.endpoints.available = multiHost.sort(state.endpoints.available);
  
                  return this.connect();
              }
  
              // If "resolveSrv" is enabled, it means we need to retrieve the
              // ordered list of endpoints from a discovery service potentially
              // available at that host.
              return srv.lookup(state.endpoints.available[0].host)
                  .then(endpoints => {
                      // We now have the effective ordered list of endpoints
                      // which we use to update the previous one.
                      state.endpoints.available = srv.sort(endpoints);
  
                      return this.connect();
                  });
          },
  
          /**
           * Resets and re-uses the underlying X Protocol connection.
           * @private
           * @function
           * @name module:Connection#override
           * @returns {Promise<module:Connection>}
           */
          override () {
              // A connection pool calls this method when it wants to re-use an
              // existing connection.
              return state.client.sessionReset()
                  .then(() => {
                      return this;
                  });
          },
  
          /**
           * Resets the internal state of the connection.
           * @private
           * @function
           * @name module:Connection#reset
           * @returns {Promise<module:Connection>}
           */
          reset () {
              // The connection capabilities will no longer be up-to-date.
              state.capabilities = {};
              // The client instance contains the work queue, which might not be
              // empty, and needs to be dereferenced anyway.
              state.client = null;
              // The connection is not closing anymore.
              state.isClosing = false;
              // An existing server connection id is what tells if a connection
              // has been successfully established, so we need to deference it
              // as well, since the connection is closed by this point.
              state.serverId = null;
              // Any existing references to prepared statements associated to
              // the connection should be removed.
              state.statements = [];
              // Any unknown capabilities are also no longer valid.
              state.unknownCapabilities = [];
              // The connection has closed, so there are no retries left.
              state.retry = false;
  
              return this;
          },
  
          /**
           * Updates the underlying X Protocol client instance.
           * @private
           * @function
           * @name module:Connection#setClient
           * @returns {Promise<module:Connection>}
           */
          setClient (client) {
              state.client = client;
  
              return this;
          },
  
          /**
           * Executes the pipeline for creating a server-side X Protocol session.
           * @private
           * @function
           * @name module:Connection#start
           * @returns {Promise<module:Connection>}
           */
          start () {
              // Start the pipeline to create a new MySQL server session.
              return this.capabilitiesSet()
                  .then(capabilities => {
                      // If TLS should be disabled, there is nothing else to do
                      // and we can proceed with the next pipeline stage.
                      if (!capabilities.tls) {
                          return;
                      }
  
                      // Otherwise, we need to create a secure socket.
                      return this.enableTLS();
                  })
                  .then(() => {
                      return this.capabilitiesGet();
                  })
                  .then(capabilities => {
                      // We should save the effective list of capabilities
                      // negotiated with the server.
                      return this.addCapabilities(capabilities);
                  })
                  .then(() => {
                      // Then we proceed to authenticate the user.
                      return this.authenticate();
                  });
          },
  
          /**
           * Updates the availability of the possible MySQL endpoints specified
           * for the connection.
           * @private
           * @function
           * @name module:Connection#update
           * @returns {module:Connection}
           */
          update () {
              const now = Date.now();
              // Check which unavailable endpoints can be re-tried.
              // If the current element in the list is not "retryable", neither
              // are the remaining. A retryable endpoint is any endpoint that has
              // been unavailable since at least the time interval defined by
              // state.retryAfter.
              while (state.endpoints.unavailable.length && now - state.endpoints.unavailable[0].unavailableAt > state.retryAfter) {
                  // If an endpoint can be re-tried we need to move it back to
                  // the list of available endpoints.
                  state.endpoints.available.push(state.endpoints.unavailable.shift());
              }
  
              return this;
          }
      };
  }
  
  Connection.validate = function ({ connectionAttributes, connectTimeout, dbUser, dbPassword, endpoints, host, port, resolveSrv, socket, ssl, sslOptions, tls }) {
      // TODO(Rui): Remove after deprecation period (undetermined).
      deprecate({ dbUser, dbPassword, ssl, sslOptions });
  
      const ports = endpoints && endpoints.length ? endpoints.map(e => e.port) : [port];
  
      // The port must be an integer between 0 and 65536.
      if (ports.some(p => !isValidInteger({ value: p, min: 0, max: 65536 }))) {
          throw new Error(errors.MESSAGES.ER_DEVAPI_BAD_CONNECTION_PORT_RANGE);
      }
  
      // We delegate validation of the TLS-related options to the secureContext
      // module.
      secureContext.validate({ tls, ssl, sslOptions });
  
      // The value of "connectTimeout" should be used to set the TCP
      // socket timeout and must be a positive integer (including 0).
      if (!isValidInteger({ value: connectTimeout, min: 0 })) {
          throw new Error(errors.MESSAGES.ER_DEVAPI_BAD_CONNECTION_TIMEOUT);
      }
  
      // If the custom application attributes are "undefined", we still
      // need to send the default client attributes to the server.
      // Otherwise, the custom application attributes must be defined
      // using a plain JavaScript object, false if they are to be
      // disabled or true, if they are to be ignored.
      if (!isValidBoolean({ value: connectionAttributes }) && !isValidPlainObject({ value: connectionAttributes })) {
          throw new Error(errors.MESSAGES.ER_DEVAPI_BAD_SESSION_ATTRIBUTES_DEFINITION);
      }
  
      // If there are custom application attributes we need to validate their
      // name. Before doing that, we need to make sure we are not bothered by "undefined".
      if (Object.keys(Object.assign({}, connectionAttributes)).some(key => key.startsWith('_'))) {
          throw new Error(errors.MESSAGES.ER_DEVAPI_BAD_SESSION_ATTRIBUTE_NAME);
      }
  
      // We delegate validation of the SRV-related options to the srv module.
      srv.validate({ resolveSrv, endpoints, host, port, socket });
  
      return true;
  };
  
  Connection.VALID_OPTIONS = VALID_OPTIONS;
  Connection.CLIENT_SESSION_ATTRIBUTES = CLIENT_SESSION_ATTRIBUTES;
  
  module.exports = Connection;
  