/*
  +----------------------------------------------------------------------+
  | Copyright (c) The PHP Group                                          |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | https://www.php.net/license/3_01.txt                                 |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Authors: Andrey Hristov <andrey@php.net>                             |
  |          Ulf Wendel <uw@php.net>                                     |
  +----------------------------------------------------------------------+
*/

#include "php.h"
#include "mysqlnd.h"
#include "mysqlnd_priv.h"
#include "mysqlnd_statistics.h"
#include "mysqlnd_debug.h"
#include "mysqlnd_ext_plugin.h"
#include "php_network.h"

#ifndef PHP_WIN32
#include <netinet/tcp.h>
#else
#include <winsock2.h>
#endif


/* {{{ mysqlnd_set_sock_no_delay */
static int
mysqlnd_set_sock_no_delay(php_stream * stream)
{
	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
	int ret = SUCCESS;
	int flag = 1;
	int result = setsockopt(socketd, IPPROTO_TCP,  TCP_NODELAY, (char *) &flag, sizeof(int));

	DBG_ENTER("mysqlnd_set_sock_no_delay");

	if (result == -1) {
		ret = FAILURE;
	}

	DBG_RETURN(ret);
}
/* }}} */


/* {{{ mysqlnd_set_sock_keepalive */
static int
mysqlnd_set_sock_keepalive(php_stream * stream)
{
	int socketd = ((php_netstream_data_t*)stream->abstract)->socket;
	int ret = SUCCESS;
	int flag = 1;
	int result = setsockopt(socketd, SOL_SOCKET, SO_KEEPALIVE, (char *) &flag, sizeof(int));

	DBG_ENTER("mysqlnd_set_sock_keepalive");

	if (result == -1) {
		ret = FAILURE;
	}

	DBG_RETURN(ret);
}
/* }}} */


/* {{{ mysqlnd_vio::network_read */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, network_read)(MYSQLND_VIO * const vio, zend_uchar * const buffer, const size_t count,
										  MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
{
	enum_func_status return_value = PASS;
	php_stream * net_stream = vio->data->m.get_stream(vio);
	size_t to_read = count;
	zend_uchar * p = buffer;

	DBG_ENTER("mysqlnd_vio::network_read");
	DBG_INF_FMT("count=%zu", count);

	while (to_read) {
		ssize_t ret = php_stream_read(net_stream, (char *) p, to_read);
		if (ret <= 0) {
			DBG_ERR_FMT("Error while reading header from socket");
			return_value = FAIL;
			break;
		}
		p += ret;
		to_read -= ret;
	}
	MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read);
	DBG_RETURN(return_value);
}
/* }}} */


/* {{{ mysqlnd_vio::network_write */
static ssize_t
MYSQLND_METHOD(mysqlnd_vio, network_write)(MYSQLND_VIO * const vio, const zend_uchar * const buffer, const size_t count,
										   MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
{
	ssize_t ret;
	DBG_ENTER("mysqlnd_vio::network_write");
	DBG_INF_FMT("sending %zu bytes", count);
	ret = php_stream_write(vio->data->m.get_stream(vio), (char *)buffer, count);
	DBG_RETURN(ret);
}
/* }}} */

static void mysqlnd_fixup_regular_list(php_stream *net_stream)
{
	/*
	  Streams are not meant for C extensions! Thus we need a hack. Every connected stream will
	  be registered as resource (in EG(regular_list). So far, so good. However, it won't be
	  unregistered until the script ends. So, we need to take care of that.
	*/
	dtor_func_t origin_dtor = EG(regular_list).pDestructor;
	EG(regular_list).pDestructor = NULL;
	zend_hash_index_del(&EG(regular_list), net_stream->res->handle);
	EG(regular_list).pDestructor = origin_dtor;
	efree(net_stream->res);
	net_stream->res = NULL;
}

/* {{{ mysqlnd_vio::open_pipe */
static php_stream *
MYSQLND_METHOD(mysqlnd_vio, open_pipe)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
									   MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
{
	unsigned int streams_options = 0;
	php_stream * net_stream = NULL;

	DBG_ENTER("mysqlnd_vio::open_pipe");
	if (persistent) {
		streams_options |= STREAM_OPEN_PERSISTENT;
	}
	streams_options |= IGNORE_URL;
	net_stream = php_stream_open_wrapper(scheme.s + sizeof("pipe://") - 1, "r+", streams_options, NULL);
	if (!net_stream) {
		SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown error while connecting");
		DBG_RETURN(NULL);
	}

	if (persistent) {
		/* This is a similar hack as for mysqlnd_vio::open_tcp_or_unix.
		 * The main difference here is that we have no access to the hashed key.
		 * We can however perform a loop over the persistent resource list to find
		 * which one corresponds to our newly allocated stream.
		 * This loop is pretty cheap because it will normally either be the last entry or second to last entry
		 * in the list, depending on whether the socket connection itself is persistent or not.
		 * That's why we use a reverse loop. */
		Bucket *bucket;
		/* Use a bucket loop to make deletion cheap. */
		ZEND_HASH_MAP_REVERSE_FOREACH_BUCKET(&EG(persistent_list), bucket) {
			zend_resource *current_res = Z_RES(bucket->val);
			if (current_res->ptr == net_stream) {
				dtor_func_t origin_dtor = EG(persistent_list).pDestructor;
				EG(persistent_list).pDestructor = NULL;
				zend_hash_del_bucket(&EG(persistent_list), bucket);
				EG(persistent_list).pDestructor = origin_dtor;
				pefree(current_res, 1);
				break;
			}
		} ZEND_HASH_FOREACH_END();
#if ZEND_DEBUG
		php_stream_auto_cleanup(net_stream);
#endif
	}

	mysqlnd_fixup_regular_list(net_stream);

	DBG_RETURN(net_stream);
}
/* }}} */


/* {{{ mysqlnd_vio::open_tcp_or_unix */
static php_stream *
MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
											  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
{
	unsigned int streams_options = 0;
	unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
	char * hashed_details = NULL;
	int hashed_details_len = 0;
	zend_string *errstr = NULL;
	int errcode = 0;
	struct timeval tv;
	dtor_func_t origin_dtor;
	php_stream * net_stream = NULL;

	DBG_ENTER("mysqlnd_vio::open_tcp_or_unix");

	vio->data->stream = NULL;

	if (persistent) {
		hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", vio);
		DBG_INF_FMT("hashed_details=%s", hashed_details);
	}

	if (vio->data->options.timeout_connect) {
		tv.tv_sec = vio->data->options.timeout_connect;
		tv.tv_usec = 0;
	}

	DBG_INF_FMT("calling php_stream_xport_create");
	net_stream = php_stream_xport_create(scheme.s, scheme.l, streams_options, streams_flags,
										  hashed_details, (vio->data->options.timeout_connect) ? &tv : NULL,
										  NULL /*ctx*/, &errstr, &errcode);
	if (errstr || !net_stream) {
		DBG_ERR("Error");
		if (hashed_details) {
			mnd_sprintf_free(hashed_details);
		}
		errcode = CR_CONNECTION_ERROR;
		SET_CLIENT_ERROR(error_info,
						 CR_CONNECTION_ERROR,
						 UNKNOWN_SQLSTATE,
						 errstr? ZSTR_VAL(errstr):"Unknown error while connecting");
		if (errstr) {
			zend_string_release_ex(errstr, 0);
		}
		DBG_RETURN(NULL);
	}
	if (hashed_details) {
		/*
		  If persistent, the streams register it in EG(persistent_list).
		  This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
		  whatever they have to.
		*/
		zend_resource *le;

		if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_len))) {
			ZEND_ASSERT(le->ptr == net_stream);
			origin_dtor = EG(persistent_list).pDestructor;
			/*
			  in_free will let streams code skip destructing - big HACK,
			  but STREAMS suck big time regarding persistent streams.
			  Just not compatible for extensions that need persistency.
			*/
			EG(persistent_list).pDestructor = NULL;
			zend_hash_str_del(&EG(persistent_list), hashed_details, hashed_details_len);
			EG(persistent_list).pDestructor = origin_dtor;
			pefree(le, 1);
		}
#if ZEND_DEBUG
		/* Shut-up the streams, they don't know what they are doing */
		php_stream_auto_cleanup(net_stream);
#endif
		mnd_sprintf_free(hashed_details);
	}

	mysqlnd_fixup_regular_list(net_stream);
	DBG_RETURN(net_stream);
}
/* }}} */


/* {{{ mysqlnd_vio::post_connect_set_opt */
static void
MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
												  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
{
	php_stream * net_stream = vio->data->m.get_stream(vio);
	DBG_ENTER("mysqlnd_vio::post_connect_set_opt");
	if (net_stream) {
		if (vio->data->options.timeout_read) {
			struct timeval tv;
			DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", vio->data->options.timeout_read);
			tv.tv_sec = vio->data->options.timeout_read;
			tv.tv_usec = 0;
			php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
		}

		if (!memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1)) {
			/* TCP -> Set TCP_NODELAY */
			mysqlnd_set_sock_no_delay(net_stream);
			/* TCP -> Set SO_KEEPALIVE */
			mysqlnd_set_sock_keepalive(net_stream);
		}

		net_stream->chunk_size = vio->data->options.net_read_buffer_size;
		net_stream->flags |= PHP_STREAM_FLAG_SUPPRESS_ERRORS;
	}

	DBG_VOID_RETURN;
}
/* }}} */


/* {{{ mysqlnd_vio::get_open_stream */
static func_mysqlnd_vio__open_stream
MYSQLND_METHOD(mysqlnd_vio, get_open_stream)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme,
											 MYSQLND_ERROR_INFO * const error_info)
{
	func_mysqlnd_vio__open_stream ret = NULL;
	DBG_ENTER("mysqlnd_vio::get_open_stream");
	if (scheme.l > (sizeof("pipe://") - 1) && !memcmp(scheme.s, "pipe://", sizeof("pipe://") - 1)) {
		ret = vio->data->m.open_pipe;
	} else if ((scheme.l > (sizeof("tcp://") - 1) && !memcmp(scheme.s, "tcp://", sizeof("tcp://") - 1))
				||
				(scheme.l > (sizeof("unix://") - 1) && !memcmp(scheme.s, "unix://", sizeof("unix://") - 1)))
	{
		ret = vio->data->m.open_tcp_or_unix;
	}

	if (!ret) {
		SET_CLIENT_ERROR(error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "No handler for this scheme");
	}

	DBG_RETURN(ret);
}
/* }}} */


/* {{{ mysqlnd_vio::connect */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, connect)(MYSQLND_VIO * const vio, const MYSQLND_CSTRING scheme, const bool persistent,
									 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
{
	enum_func_status ret = FAIL;
	func_mysqlnd_vio__open_stream open_stream = NULL;
	DBG_ENTER("mysqlnd_vio::connect");

	vio->data->m.close_stream(vio, conn_stats, error_info);

	open_stream = vio->data->m.get_open_stream(vio, scheme, error_info);
	if (open_stream) {
		php_stream * net_stream = open_stream(vio, scheme, persistent, conn_stats, error_info);
		if (net_stream && PASS == vio->data->m.set_stream(vio, net_stream)) {
			vio->data->m.post_connect_set_opt(vio, scheme, conn_stats, error_info);
			ret = PASS;
		}
	}

	DBG_RETURN(ret);
}
/* }}} */


/* {{{ mysqlnd_vio::set_client_option */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, set_client_option)(MYSQLND_VIO * const net, enum_mysqlnd_client_option option, const char * const value)
{
	DBG_ENTER("mysqlnd_vio::set_client_option");
	DBG_INF_FMT("option=%u", option);
	switch (option) {
		case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
			DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE");
			net->data->options.net_read_buffer_size = *(unsigned int*) value;
			DBG_INF_FMT("new_length=%zu", net->data->options.net_read_buffer_size);
			break;
		case MYSQL_OPT_CONNECT_TIMEOUT:
			DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT");
			net->data->options.timeout_connect = *(unsigned int*) value;
			break;
		case MYSQLND_OPT_SSL_KEY:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_key) {
					mnd_pefree(net->data->options.ssl_key, pers);
				}
				net->data->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQLND_OPT_SSL_CERT:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_cert) {
					mnd_pefree(net->data->options.ssl_cert, pers);
				}
				net->data->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQLND_OPT_SSL_CA:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_ca) {
					mnd_pefree(net->data->options.ssl_ca, pers);
				}
				net->data->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQLND_OPT_SSL_CAPATH:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_capath) {
					mnd_pefree(net->data->options.ssl_capath, pers);
				}
				net->data->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQLND_OPT_SSL_CIPHER:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_cipher) {
					mnd_pefree(net->data->options.ssl_cipher, pers);
				}
				net->data->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQLND_OPT_SSL_PASSPHRASE:
			{
				bool pers = net->persistent;
				if (net->data->options.ssl_passphrase) {
					mnd_pefree(net->data->options.ssl_passphrase, pers);
				}
				net->data->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL;
				break;
			}
		case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
		{
			enum mysqlnd_ssl_peer val = *((enum mysqlnd_ssl_peer *)value);
			switch (val) {
				case MYSQLND_SSL_PEER_VERIFY:
					DBG_INF("MYSQLND_SSL_PEER_VERIFY");
					break;
				case MYSQLND_SSL_PEER_DONT_VERIFY:
					DBG_INF("MYSQLND_SSL_PEER_DONT_VERIFY");
					break;
				case MYSQLND_SSL_PEER_DEFAULT:
					DBG_INF("MYSQLND_SSL_PEER_DEFAULT");
					val = MYSQLND_SSL_PEER_DEFAULT;
					break;
				default:
					DBG_INF("default = MYSQLND_SSL_PEER_DEFAULT_ACTION");
					val = MYSQLND_SSL_PEER_DEFAULT;
					break;
			}
			net->data->options.ssl_verify_peer = val;
			break;
		}
		case MYSQL_OPT_READ_TIMEOUT:
			net->data->options.timeout_read = *(unsigned int*) value;
			break;
#ifdef WHEN_SUPPORTED_BY_MYSQLI
		case MYSQL_OPT_WRITE_TIMEOUT:
			net->data->options.timeout_write = *(unsigned int*) value;
			break;
#endif
		default:
			DBG_RETURN(FAIL);
	}
	DBG_RETURN(PASS);
}
/* }}} */


/* {{{ mysqlnd_vio::consume_uneaten_data */
size_t
MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data)(MYSQLND_VIO * const net, enum php_mysqlnd_server_command cmd)
{
#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND
	/*
	  Switch to non-blocking mode and try to consume something from
	  the line, if possible, then continue. This saves us from looking for
	  the actual place where out-of-order packets have been sent.
	  If someone is completely sure that everything is fine, he can switch it
	  off.
	*/
	char tmp_buf[256];
	size_t skipped_bytes = 0;
	int opt = PHP_STREAM_OPTION_BLOCKING;
	php_stream * net_stream = net->data->get_stream(net);
	int was_blocked = net_stream->ops->set_option(net_stream, opt, 0, NULL);

	DBG_ENTER("mysqlnd_vio::consume_uneaten_data");

	if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) {
		/* Do a read of 1 byte */
		ssize_t bytes_consumed;

		do {
			bytes_consumed = php_stream_read(net_stream, tmp_buf, sizeof(tmp_buf));
			if (bytes_consumed <= 0) {
				break;
			}
			skipped_bytes += bytes_consumed;
		} while (bytes_consumed == sizeof(tmp_buf));

		if (was_blocked) {
			net_stream->ops->set_option(net_stream, opt, 1, NULL);
		}

		if (bytes_consumed) {
			DBG_ERR_FMT("Skipped %u bytes. Last command hasn't consumed all the output from the server",
						bytes_consumed, mysqlnd_command_to_text[net->last_command]);
			php_error_docref(NULL, E_WARNING, "Skipped %u bytes. Last command %s hasn't "
							 "consumed all the output from the server",
							 bytes_consumed, mysqlnd_command_to_text[net->last_command]);
		}
	}
	net->last_command = cmd;

	DBG_RETURN(skipped_bytes);
#else
	return 0;
#endif
}
/* }}} */

/*
  in libmyusql, if cert and !key then key=cert
*/
/* {{{ mysqlnd_vio::enable_ssl */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, enable_ssl)(MYSQLND_VIO * const net)
{
#ifdef MYSQLND_SSL_SUPPORTED
	php_stream_context * context = php_stream_context_alloc();
	php_stream * net_stream = net->data->m.get_stream(net);
	bool any_flag = FALSE;

	DBG_ENTER("mysqlnd_vio::enable_ssl");

	if (net->data->options.ssl_key) {
		zval key_zval;
		ZVAL_STRING(&key_zval, net->data->options.ssl_key);
		php_stream_context_set_option(context, "ssl", "local_pk", &key_zval);
		zval_ptr_dtor(&key_zval);
		any_flag = TRUE;
	}
	if (net->data->options.ssl_cert) {
		zval cert_zval;
		ZVAL_STRING(&cert_zval, net->data->options.ssl_cert);
		php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval);
		if (!net->data->options.ssl_key) {
			php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval);
		}
		zval_ptr_dtor(&cert_zval);
		any_flag = TRUE;
	}
	if (net->data->options.ssl_ca) {
		zval cafile_zval;
		ZVAL_STRING(&cafile_zval, net->data->options.ssl_ca);
		php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval);
		zval_ptr_dtor(&cafile_zval);
		any_flag = TRUE;
	}
	if (net->data->options.ssl_capath) {
		zval capath_zval;
		ZVAL_STRING(&capath_zval, net->data->options.ssl_capath);
		php_stream_context_set_option(context, "ssl", "capath", &capath_zval);
		zval_ptr_dtor(&capath_zval);
		any_flag = TRUE;
	}
	if (net->data->options.ssl_passphrase) {
		zval passphrase_zval;
		ZVAL_STRING(&passphrase_zval, net->data->options.ssl_passphrase);
		php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval);
		zval_ptr_dtor(&passphrase_zval);
		any_flag = TRUE;
	}
	if (net->data->options.ssl_cipher) {
		zval cipher_zval;
		ZVAL_STRING(&cipher_zval, net->data->options.ssl_cipher);
		php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval);
		zval_ptr_dtor(&cipher_zval);
		any_flag = TRUE;
	}
	{
		zval verify_peer_zval;
		bool verify;

		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DEFAULT) {
			net->data->options.ssl_verify_peer = any_flag? MYSQLND_SSL_PEER_DEFAULT_ACTION:MYSQLND_SSL_PEER_DONT_VERIFY;
		}

		verify = net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_VERIFY? TRUE:FALSE;

		DBG_INF_FMT("VERIFY=%d", verify);
		ZVAL_BOOL(&verify_peer_zval, verify);
		php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval);
		php_stream_context_set_option(context, "ssl", "verify_peer_name", &verify_peer_zval);
		if (net->data->options.ssl_verify_peer == MYSQLND_SSL_PEER_DONT_VERIFY) {
			ZVAL_TRUE(&verify_peer_zval);
			php_stream_context_set_option(context, "ssl", "allow_self_signed", &verify_peer_zval);
		}
	}
	php_stream_context_set(net_stream, context);
	/* php_stream_context_set() increases the refcount of context, but we just want to transfer ownership
	 * hence the need to decrease the refcount so the refcount will be equal to 1. */
	ZEND_ASSERT(GC_REFCOUNT(context->res) == 2);
	GC_DELREF(context->res);
	if (php_stream_xport_crypto_setup(net_stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL) < 0 ||
	    php_stream_xport_crypto_enable(net_stream, 1) < 0)
	{
		DBG_ERR("Cannot connect to MySQL by using SSL");
		DBG_RETURN(FAIL);
	}
	net->data->ssl = TRUE;
	/*
	  get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli,
	  then the context would not survive cleaning of EG(regular_list), where it is registered, as a
	  resource. What happens is that after this destruction any use of the network will mean usage
	  of the context, which means usage of already freed memory, bad. Actually we don't need this
	  context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it.
	*/
	php_stream_context_set(net_stream, NULL);

	if (net->data->options.timeout_read) {
		struct timeval tv;
		DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->data->options.timeout_read);
		tv.tv_sec = net->data->options.timeout_read;
		tv.tv_usec = 0;
		php_stream_set_option(net_stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
	}

	DBG_RETURN(PASS);
#else
	DBG_ENTER("mysqlnd_vio::enable_ssl");
	DBG_INF("MYSQLND_SSL_SUPPORTED is not defined");
	DBG_RETURN(PASS);
#endif
}
/* }}} */


/* {{{ mysqlnd_vio::disable_ssl */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, disable_ssl)(MYSQLND_VIO * const vio)
{
	DBG_ENTER("mysqlnd_vio::disable_ssl");
	DBG_RETURN(PASS);
}
/* }}} */


/* {{{ mysqlnd_vio::free_contents */
static void
MYSQLND_METHOD(mysqlnd_vio, free_contents)(MYSQLND_VIO * net)
{
	bool pers = net->persistent;
	DBG_ENTER("mysqlnd_vio::free_contents");

	if (net->data->options.ssl_key) {
		mnd_pefree(net->data->options.ssl_key, pers);
		net->data->options.ssl_key = NULL;
	}
	if (net->data->options.ssl_cert) {
		mnd_pefree(net->data->options.ssl_cert, pers);
		net->data->options.ssl_cert = NULL;
	}
	if (net->data->options.ssl_ca) {
		mnd_pefree(net->data->options.ssl_ca, pers);
		net->data->options.ssl_ca = NULL;
	}
	if (net->data->options.ssl_capath) {
		mnd_pefree(net->data->options.ssl_capath, pers);
		net->data->options.ssl_capath = NULL;
	}
	if (net->data->options.ssl_cipher) {
		mnd_pefree(net->data->options.ssl_cipher, pers);
		net->data->options.ssl_cipher = NULL;
	}

	DBG_VOID_RETURN;
}
/* }}} */


/* {{{ mysqlnd_vio::close_stream */
static void
MYSQLND_METHOD(mysqlnd_vio, close_stream)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
{
	php_stream * net_stream;
	DBG_ENTER("mysqlnd_vio::close_stream");
	if (net && (net_stream = net->data->m.get_stream(net))) {
		bool pers = net->persistent;
		DBG_INF_FMT("Freeing stream. abstract=%p", net_stream->abstract);
		/* We removed the resource from the stream, so pass FREE_RSRC_DTOR now to force
		 * destruction to occur during shutdown, because it won't happen through the resource
		 * because we removed the resource from the EG resource list(s). */
		if (pers) {
			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
		} else {
			php_stream_free(net_stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR);
		}
		net->data->m.set_stream(net, NULL);
	}

	DBG_VOID_RETURN;
}
/* }}} */


/* {{{ mysqlnd_vio::init */
static void
MYSQLND_METHOD(mysqlnd_vio, init)(MYSQLND_VIO * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
{
	unsigned int buf_size;
	DBG_ENTER("mysqlnd_vio::init");

	buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/
	net->data->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size);

	buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/
	net->data->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size);

	DBG_VOID_RETURN;
}
/* }}} */


/* {{{ mysqlnd_vio::dtor */
static void
MYSQLND_METHOD(mysqlnd_vio, dtor)(MYSQLND_VIO * const vio, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
{
	DBG_ENTER("mysqlnd_vio::dtor");
	if (vio) {
		vio->data->m.free_contents(vio);
		vio->data->m.close_stream(vio, stats, error_info);

		mnd_pefree(vio, vio->persistent);
	}
	DBG_VOID_RETURN;
}
/* }}} */


/* {{{ mysqlnd_vio::get_stream */
static php_stream *
MYSQLND_METHOD(mysqlnd_vio, get_stream)(const MYSQLND_VIO * const net)
{
	DBG_ENTER("mysqlnd_vio::get_stream");
	DBG_INF_FMT("%p", net? net->data->stream:NULL);
	DBG_RETURN(net? net->data->stream:NULL);
}
/* }}} */


/* {{{ mysqlnd_vio::set_stream */
static enum_func_status
MYSQLND_METHOD(mysqlnd_vio, set_stream)(MYSQLND_VIO * const vio, php_stream * net_stream)
{
	DBG_ENTER("mysqlnd_vio::set_stream");
	if (vio) {
		vio->data->stream = net_stream;
		DBG_RETURN(PASS);
	}
	DBG_RETURN(FAIL);
}
/* }}} */


/* {{{ mysqlnd_vio::has_valid_stream */
static bool
MYSQLND_METHOD(mysqlnd_vio, has_valid_stream)(const MYSQLND_VIO * const vio)
{
	DBG_ENTER("mysqlnd_vio::has_valid_stream");
	DBG_INF_FMT("%p %p", vio, vio? vio->data->stream:NULL);
	DBG_RETURN((vio && vio->data->stream)? TRUE: FALSE);
}
/* }}} */


MYSQLND_CLASS_METHODS_START(mysqlnd_vio)
	MYSQLND_METHOD(mysqlnd_vio, init),
	MYSQLND_METHOD(mysqlnd_vio, dtor),

	MYSQLND_METHOD(mysqlnd_vio, connect),

	MYSQLND_METHOD(mysqlnd_vio, close_stream),
	MYSQLND_METHOD(mysqlnd_vio, open_pipe),
	MYSQLND_METHOD(mysqlnd_vio, open_tcp_or_unix),

	MYSQLND_METHOD(mysqlnd_vio, get_stream),
	MYSQLND_METHOD(mysqlnd_vio, set_stream),
	MYSQLND_METHOD(mysqlnd_vio, has_valid_stream),
	MYSQLND_METHOD(mysqlnd_vio, get_open_stream),

	MYSQLND_METHOD(mysqlnd_vio, set_client_option),
	MYSQLND_METHOD(mysqlnd_vio, post_connect_set_opt),

	MYSQLND_METHOD(mysqlnd_vio, enable_ssl),
	MYSQLND_METHOD(mysqlnd_vio, disable_ssl),

	MYSQLND_METHOD(mysqlnd_vio, network_read),
	MYSQLND_METHOD(mysqlnd_vio, network_write),

	MYSQLND_METHOD(mysqlnd_vio, consume_uneaten_data),

	MYSQLND_METHOD(mysqlnd_vio, free_contents),
MYSQLND_CLASS_METHODS_END;


/* {{{ mysqlnd_vio_init */
PHPAPI MYSQLND_VIO *
mysqlnd_vio_init(bool persistent, MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *object_factory, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
{
	MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *factory = object_factory? object_factory : &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory);
	MYSQLND_VIO * vio;
	DBG_ENTER("mysqlnd_vio_init");
	vio = factory->get_vio(persistent, stats, error_info);
	DBG_RETURN(vio);
}
/* }}} */


/* {{{ mysqlnd_vio_free */
PHPAPI void
mysqlnd_vio_free(MYSQLND_VIO * const vio, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
{
	DBG_ENTER("mysqlnd_vio_free");
	if (vio) {
		vio->data->m.dtor(vio, stats, error_info);
	}
	DBG_VOID_RETURN;
}
/* }}} */
