1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) The PHP Group                                          |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | https://www.php.net/license/3_01.txt                                 |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Authors: Andrey Hristov <andrey@php.net>                             |
14   |          Ulf Wendel <uw@php.net>                                     |
15   +----------------------------------------------------------------------+
16 */
17 
18 #include "php.h"
19 #include "mysqlnd.h"
20 #include "mysqlnd_connection.h"
21 #include "mysqlnd_priv.h"
22 #include "mysqlnd_read_buffer.h"
23 #include "mysqlnd_wireprotocol.h"
24 #include "mysqlnd_statistics.h"
25 #include "mysqlnd_debug.h"
26 #ifdef MYSQLND_COMPRESSION_ENABLED
27 #include <zlib.h>
28 #endif
29 
30 
31 /* {{{ mysqlnd_pfc::reset */
32 static void
MYSQLND_METHOD(mysqlnd_pfc,reset)33 MYSQLND_METHOD(mysqlnd_pfc, reset)(MYSQLND_PFC * const pfc, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
34 {
35 	DBG_ENTER("mysqlnd_pfc::reset");
36 	pfc->data->packet_no = pfc->data->compressed_envelope_packet_no = 0;
37 	DBG_VOID_RETURN;
38 }
39 /* }}} */
40 
41 
42 /* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */
43 #define COPY_HEADER(T,A)  do { \
44 		*(((char *)(T)))   = *(((char *)(A)));\
45 		*(((char *)(T))+1) = *(((char *)(A))+1);\
46 		*(((char *)(T))+2) = *(((char *)(A))+2);\
47 		*(((char *)(T))+3) = *(((char *)(A))+3); } while (0)
48 #define STORE_HEADER_SIZE(safe_storage, buffer)  COPY_HEADER((safe_storage), (buffer))
49 #define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
50 
51 #ifdef MYSQLND_COMPRESSION_ENABLED
write_compressed_packet(const MYSQLND_PFC * pfc,MYSQLND_VIO * vio,MYSQLND_STATS * conn_stats,MYSQLND_ERROR_INFO * error_info,zend_uchar * uncompressed_payload,size_t to_be_sent,zend_uchar * compress_buf)52 static ssize_t write_compressed_packet(
53 		const MYSQLND_PFC *pfc, MYSQLND_VIO *vio,
54 		MYSQLND_STATS *conn_stats, MYSQLND_ERROR_INFO *error_info,
55 		zend_uchar *uncompressed_payload, size_t to_be_sent, zend_uchar *compress_buf) {
56 	DBG_ENTER("write_compressed_packet");
57 	/* here we need to compress the data and then write it, first comes the compressed header */
58 	size_t tmp_complen = to_be_sent;
59 	size_t payload_size;
60 	if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
61 							   uncompressed_payload, to_be_sent))
62 	{
63 		int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent);
64 		payload_size = tmp_complen;
65 	} else {
66 		int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
67 		memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent);
68 		payload_size = to_be_sent;
69 	}
70 
71 	int3store(compress_buf, payload_size);
72 	int1store(compress_buf + 3, pfc->data->compressed_envelope_packet_no);
73 	DBG_INF_FMT("writing %zu bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
74 
75 	ssize_t bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
76 	pfc->data->compressed_envelope_packet_no++;
77 #ifdef WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
78 	if (res == Z_OK) {
79 		size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
80 		zend_uchar * decompressed_data = mnd_emalloc(decompressed_size);
81 		int error = pfc->data->m.decode(decompressed_data, decompressed_size,
82 										compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
83 		if (error == Z_OK) {
84 			int i;
85 			DBG_INF("success decompressing");
86 			for (i = 0 ; i < decompressed_size; i++) {
87 				if (i && (i % 30 == 0)) {
88 					printf("\n\t\t");
89 				}
90 				printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
91 				DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
92 			}
93 		} else {
94 			DBG_INF("error decompressing");
95 		}
96 		mnd_efree(decompressed_data);
97 	}
98 #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
99 	DBG_RETURN(bytes_sent);
100 }
101 #endif
102 
103 /* {{{ mysqlnd_pfc::send */
104 /*
105   IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!!
106 			  This is done for performance reasons in the caller of this function.
107 			  Otherwise we will have to do send two TCP packets, or do new alloc and memcpy.
108 			  Neither are quick, thus the clients of this function are obligated to do
109 			  what they are asked for.
110 
111   `count` is actually the length of the payload data. Thus :
112   count + MYSQLND_HEADER_SIZE = sizeof(buffer) (not the pointer but the actual buffer)
113 */
114 static size_t
MYSQLND_METHOD(mysqlnd_pfc,send)115 MYSQLND_METHOD(mysqlnd_pfc, send)(MYSQLND_PFC * const pfc, MYSQLND_VIO * const vio, zend_uchar * const buffer, const size_t count,
116 								  MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
117 {
118 	zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))];
119 	zend_uchar * safe_storage = safe_buf;
120 	size_t packets_sent = 1;
121 	size_t left = count;
122 	zend_uchar * p = (zend_uchar *) buffer;
123 	zend_uchar * compress_buf = NULL;
124 	size_t to_be_sent;
125 	ssize_t bytes_sent;
126 
127 	DBG_ENTER("mysqlnd_pfc::send");
128 	DBG_INF_FMT("count=%zu compression=%u", count, pfc->data->compressed);
129 
130 	if (pfc->data->compressed == TRUE) {
131 		size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE);
132 		DBG_INF_FMT("compress_buf_size=%zu", comp_buf_size);
133 		compress_buf = mnd_emalloc(comp_buf_size);
134 	}
135 
136 	do {
137 		to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE);
138 		DBG_INF_FMT("to_be_sent=%zu", to_be_sent);
139 		DBG_INF_FMT("packets_sent=%zu", packets_sent);
140 		DBG_INF_FMT("compressed_envelope_packet_no=%u", pfc->data->compressed_envelope_packet_no);
141 		DBG_INF_FMT("packet_no=%u", pfc->data->packet_no);
142 #ifdef MYSQLND_COMPRESSION_ENABLED
143 		if (pfc->data->compressed == TRUE) {
144 			zend_uchar * uncompressed_payload = p; /* should include the header */
145 			STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
146 			int3store(uncompressed_payload, to_be_sent);
147 			int1store(uncompressed_payload + 3, pfc->data->packet_no);
148 			if (to_be_sent <= MYSQLND_MAX_PACKET_SIZE - MYSQLND_HEADER_SIZE) {
149 				bytes_sent = write_compressed_packet(
150 					pfc, vio, conn_stats, error_info,
151 					uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE, compress_buf);
152 			} else {
153 				/* The uncompressed size including the header would overflow. Split into two
154 				 * compressed packets. The size of the first one is relatively arbitrary here. */
155 				const size_t split_off_bytes = 8192;
156 				bytes_sent = write_compressed_packet(
157 					pfc, vio, conn_stats, error_info,
158 					uncompressed_payload, split_off_bytes, compress_buf);
159 				bytes_sent = write_compressed_packet(
160 					pfc, vio, conn_stats, error_info,
161 					uncompressed_payload + split_off_bytes,
162 					to_be_sent + MYSQLND_HEADER_SIZE - split_off_bytes, compress_buf);
163 			}
164 			RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
165 		} else
166 #endif /* MYSQLND_COMPRESSION_ENABLED */
167 		{
168 			DBG_INF("no compression");
169 			STORE_HEADER_SIZE(safe_storage, p);
170 			int3store(p, to_be_sent);
171 			int1store(p + 3, pfc->data->packet_no);
172 			bytes_sent = vio->data->m.network_write(vio, p, to_be_sent + MYSQLND_HEADER_SIZE, conn_stats, error_info);
173 			RESTORE_HEADER_SIZE(p, safe_storage);
174 			pfc->data->compressed_envelope_packet_no++;
175 		}
176 		pfc->data->packet_no++;
177 
178 		p += to_be_sent;
179 		left -= to_be_sent;
180 		packets_sent++;
181 		/*
182 		  if left is 0 then there is nothing more to send, but if the last packet was exactly
183 		  with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has
184 		  empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is
185 		  indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty
186 		  packet will be sent and this loop will end.
187 		*/
188 	} while (bytes_sent > 0 && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE));
189 
190 	DBG_INF_FMT("packet_size=%zu packet_no=%u", left, pfc->data->packet_no);
191 
192 	MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats,
193 			STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE,
194 			STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE,
195 			STAT_PACKETS_SENT, packets_sent);
196 
197 	if (compress_buf) {
198 		mnd_efree(compress_buf);
199 	}
200 
201 	/* Even for zero size payload we have to send a packet */
202 	if (bytes_sent <= 0) {
203 		DBG_ERR_FMT("Can't %zu send bytes", count);
204 		SET_CLIENT_ERROR(error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
205 		bytes_sent = 0; // the return type is unsigned and 0 represents an error condition
206 	}
207 	DBG_RETURN(bytes_sent);
208 }
209 /* }}} */
210 
211 
212 #ifdef MYSQLND_COMPRESSION_ENABLED
213 
214 /* {{{ mysqlnd_pfc::read_compressed_packet_from_stream_and_fill_read_buffer */
215 static enum_func_status
MYSQLND_METHOD(mysqlnd_pfc,read_compressed_packet_from_stream_and_fill_read_buffer)216 MYSQLND_METHOD(mysqlnd_pfc, read_compressed_packet_from_stream_and_fill_read_buffer)
217 		(MYSQLND_PFC * pfc, MYSQLND_VIO * vio, size_t net_payload_size, MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info)
218 {
219 	size_t decompressed_size;
220 	enum_func_status retval = PASS;
221 	zend_uchar * compressed_data = NULL;
222 	zend_uchar comp_header[COMPRESSED_HEADER_SIZE];
223 	DBG_ENTER("mysqlnd_pfc::read_compressed_packet_from_stream_and_fill_read_buffer");
224 
225 	/* Read the compressed header */
226 	if (FAIL == vio->data->m.network_read(vio, comp_header, COMPRESSED_HEADER_SIZE, conn_stats, error_info)) {
227 		DBG_RETURN(FAIL);
228 	}
229 	decompressed_size = uint3korr(comp_header);
230 
231 	/* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */
232 	/* we need to decompress the data */
233 
234 	if (decompressed_size) {
235 		compressed_data = mnd_emalloc(net_payload_size);
236 		if (FAIL == vio->data->m.network_read(vio, compressed_data, net_payload_size, conn_stats, error_info)) {
237 			retval = FAIL;
238 			goto end;
239 		}
240 		pfc->data->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size);
241 		retval = pfc->data->m.decode(pfc->data->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size);
242 		if (FAIL == retval) {
243 			goto end;
244 		}
245 	} else {
246 		DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %zu bytes", net_payload_size);
247 		pfc->data->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size);
248 		if (FAIL == vio->data->m.network_read(vio, pfc->data->uncompressed_data->data, net_payload_size, conn_stats, error_info)) {
249 			retval = FAIL;
250 			goto end;
251 		}
252 	}
253 end:
254 	if (compressed_data) {
255 		mnd_efree(compressed_data);
256 	}
257 	DBG_RETURN(retval);
258 }
259 /* }}} */
260 #endif /* MYSQLND_COMPRESSION_ENABLED */
261 
262 
263 /* {{{ mysqlnd_pfc::decode */
264 static enum_func_status
MYSQLND_METHOD(mysqlnd_pfc,decode)265 MYSQLND_METHOD(mysqlnd_pfc, decode)(zend_uchar * uncompressed_data, const size_t uncompressed_data_len,
266 									const zend_uchar * const compressed_data, const size_t compressed_data_len)
267 {
268 #ifdef MYSQLND_COMPRESSION_ENABLED
269 	int error;
270 	uLongf tmp_complen = uncompressed_data_len;
271 	DBG_ENTER("mysqlnd_pfc::decode");
272 	error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len);
273 
274 	DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size=%zu", tmp_complen, compressed_data_len);
275 	if (error != Z_OK) {
276 		DBG_INF_FMT("decompression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
277 	}
278 	DBG_RETURN(error == Z_OK? PASS:FAIL);
279 #else
280 	DBG_ENTER("mysqlnd_pfc::decode");
281 	DBG_RETURN(FAIL);
282 #endif
283 }
284 /* }}} */
285 
286 
287 /* {{{ mysqlnd_pfc::encode */
288 static enum_func_status
MYSQLND_METHOD(mysqlnd_pfc,encode)289 MYSQLND_METHOD(mysqlnd_pfc, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len,
290 									const zend_uchar * const uncompressed_data, const size_t uncompressed_data_len)
291 {
292 #ifdef MYSQLND_COMPRESSION_ENABLED
293 	int error;
294 	uLongf tmp_complen = *compress_buffer_len;
295 	DBG_ENTER("mysqlnd_pfc::encode");
296 	error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len);
297 
298 	if (error != Z_OK) {
299 		DBG_INF_FMT("compression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR);
300 	} else {
301 		*compress_buffer_len = tmp_complen;
302 		DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen);
303 	}
304 
305 	DBG_RETURN(error == Z_OK? PASS:FAIL);
306 #else
307 	DBG_ENTER("mysqlnd_pfc::encode");
308 	DBG_RETURN(FAIL);
309 #endif
310 }
311 /* }}} */
312 
313 
314 /* {{{ mysqlnd_pfc::receive */
315 static enum_func_status
MYSQLND_METHOD(mysqlnd_pfc,receive)316 MYSQLND_METHOD(mysqlnd_pfc, receive)(MYSQLND_PFC * const pfc, MYSQLND_VIO * const vio, zend_uchar * const buffer, const size_t count,
317 									 MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info)
318 {
319 	size_t to_read = count;
320 	zend_uchar * p = buffer;
321 
322 	DBG_ENTER("mysqlnd_pfc::receive");
323 #ifdef MYSQLND_COMPRESSION_ENABLED
324 	if (pfc->data->compressed) {
325 		if (pfc->data->uncompressed_data) {
326 			size_t to_read_from_buffer = MIN(pfc->data->uncompressed_data->bytes_left(pfc->data->uncompressed_data), to_read);
327 			DBG_INF_FMT("reading %zu from uncompressed_data buffer", to_read_from_buffer);
328 			if (to_read_from_buffer) {
329 				pfc->data->uncompressed_data->read(pfc->data->uncompressed_data, to_read_from_buffer, (zend_uchar *) p);
330 				p += to_read_from_buffer;
331 				to_read -= to_read_from_buffer;
332 			}
333 			DBG_INF_FMT("left %zu to read", to_read);
334 			if (TRUE == pfc->data->uncompressed_data->is_empty(pfc->data->uncompressed_data)) {
335 				/* Everything was consumed. This should never happen here, but for security */
336 				pfc->data->uncompressed_data->free_buffer(&pfc->data->uncompressed_data);
337 			}
338 		}
339 		if (to_read) {
340 			zend_uchar net_header[MYSQLND_HEADER_SIZE];
341 			size_t net_payload_size;
342 			zend_uchar packet_no;
343 
344 			if (FAIL == vio->data->m.network_read(vio, net_header, MYSQLND_HEADER_SIZE, conn_stats, error_info)) {
345 				DBG_RETURN(FAIL);
346 			}
347 			net_payload_size = uint3korr(net_header);
348 			packet_no = uint1korr(net_header + 3);
349 			if (pfc->data->compressed_envelope_packet_no != packet_no) {
350 				DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size=%zu",
351 							pfc->data->compressed_envelope_packet_no, packet_no, net_payload_size);
352 
353 				php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size=%zu",
354 						  pfc->data->compressed_envelope_packet_no, packet_no, net_payload_size);
355 				DBG_RETURN(FAIL);
356 			}
357 			pfc->data->compressed_envelope_packet_no++;
358 			/* Now let's read from the wire, decompress it and fill the read buffer */
359 			pfc->data->m.read_compressed_packet_from_stream_and_fill_read_buffer(pfc, vio, net_payload_size, conn_stats, error_info);
360 
361 			/*
362 			  Now a bit of recursion - read from the read buffer,
363 			  if the data which we have just read from the wire
364 			  is not enough, then the recursive call will try to
365 			  satisfy it until it is satisfied.
366 			*/
367 			DBG_RETURN(pfc->data->m.receive(pfc, vio, p, to_read, conn_stats, error_info));
368 		}
369 		DBG_RETURN(PASS);
370 	}
371 #endif /* MYSQLND_COMPRESSION_ENABLED */
372 	DBG_RETURN(vio->data->m.network_read(vio, p, to_read, conn_stats, error_info));
373 }
374 /* }}} */
375 
376 
377 /* {{{ mysqlnd_pfc::set_client_option */
378 static enum_func_status
MYSQLND_METHOD(mysqlnd_pfc,set_client_option)379 MYSQLND_METHOD(mysqlnd_pfc, set_client_option)(MYSQLND_PFC * const pfc, enum_mysqlnd_client_option option, const char * const value)
380 {
381 	DBG_ENTER("mysqlnd_pfc::set_client_option");
382 	DBG_INF_FMT("option=%u", option);
383 	switch (option) {
384 		case MYSQL_OPT_COMPRESS:
385 			pfc->data->flags |= MYSQLND_PROTOCOL_FLAG_USE_COMPRESSION;
386 			break;
387 		case MYSQL_SERVER_PUBLIC_KEY: {
388 			const bool pers = pfc->persistent;
389 			if (pfc->data->sha256_server_public_key) {
390 				mnd_pefree(pfc->data->sha256_server_public_key, pers);
391 			}
392 			pfc->data->sha256_server_public_key = value? mnd_pestrdup(value, pers) : NULL;
393 			break;
394 		}
395 		case MYSQLND_OPT_NET_CMD_BUFFER_SIZE: {
396 			DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE");
397 			if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) {
398 				DBG_RETURN(FAIL);
399 			}
400 			pfc->cmd_buffer.length = *(unsigned int*) value;
401 			DBG_INF_FMT("new_length=%zu", pfc->cmd_buffer.length);
402 			if (!pfc->cmd_buffer.buffer) {
403 				pfc->cmd_buffer.buffer = mnd_pemalloc(pfc->cmd_buffer.length, pfc->persistent);
404 			} else {
405 				pfc->cmd_buffer.buffer = mnd_perealloc(pfc->cmd_buffer.buffer, pfc->cmd_buffer.length, pfc->persistent);
406 			}
407 			break;
408 		}
409 		default:
410 			DBG_RETURN(FAIL);
411 	}
412 	DBG_RETURN(PASS);
413 }
414 /* }}} */
415 
416 
417 /* {{{ mysqlnd_pfc::free_contents */
418 static void
MYSQLND_METHOD(mysqlnd_pfc,free_contents)419 MYSQLND_METHOD(mysqlnd_pfc, free_contents)(MYSQLND_PFC * pfc)
420 {
421 	DBG_ENTER("mysqlnd_pfc::free_contents");
422 
423 #ifdef MYSQLND_COMPRESSION_ENABLED
424 	if (pfc->data->uncompressed_data) {
425 		pfc->data->uncompressed_data->free_buffer(&pfc->data->uncompressed_data);
426 	}
427 #endif
428 	if (pfc->data->sha256_server_public_key) {
429 		mnd_pefree(pfc->data->sha256_server_public_key, pfc->persistent);
430 		pfc->data->sha256_server_public_key = NULL;
431 	}
432 
433 	DBG_VOID_RETURN;
434 }
435 /* }}} */
436 
437 
438 /* {{{ mysqlnd_pfc::init */
439 static void
MYSQLND_METHOD(mysqlnd_pfc,init)440 MYSQLND_METHOD(mysqlnd_pfc, init)(MYSQLND_PFC * const pfc, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
441 {
442 	unsigned int buf_size;
443 	DBG_ENTER("mysqlnd_pfc::init");
444 
445 	buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/
446 	pfc->data->m.set_client_option(pfc, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size);
447 
448 	DBG_VOID_RETURN;
449 }
450 /* }}} */
451 
452 
453 /* {{{ mysqlnd_pfc::dtor */
454 static void
MYSQLND_METHOD(mysqlnd_pfc,dtor)455 MYSQLND_METHOD(mysqlnd_pfc, dtor)(MYSQLND_PFC * const pfc, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info)
456 {
457 	DBG_ENTER("mysqlnd_pfc::dtor");
458 	if (pfc) {
459 		pfc->data->m.free_contents(pfc);
460 
461 		if (pfc->cmd_buffer.buffer) {
462 			DBG_INF("Freeing cmd buffer");
463 			mnd_pefree(pfc->cmd_buffer.buffer, pfc->persistent);
464 			pfc->cmd_buffer.buffer = NULL;
465 		}
466 
467 		mnd_pefree(pfc, pfc->persistent);
468 	}
469 	DBG_VOID_RETURN;
470 }
471 /* }}} */
472 
473 
474 MYSQLND_CLASS_METHODS_START(mysqlnd_protocol_packet_frame_codec)
475 	MYSQLND_METHOD(mysqlnd_pfc, init),
476 	MYSQLND_METHOD(mysqlnd_pfc, dtor),
477 	MYSQLND_METHOD(mysqlnd_pfc, reset),
478 
479 	MYSQLND_METHOD(mysqlnd_pfc, set_client_option),
480 
481 	MYSQLND_METHOD(mysqlnd_pfc, decode),
482 	MYSQLND_METHOD(mysqlnd_pfc, encode),
483 
484 	MYSQLND_METHOD(mysqlnd_pfc, send),
485 	MYSQLND_METHOD(mysqlnd_pfc, receive),
486 
487 #ifdef MYSQLND_COMPRESSION_ENABLED
488 	MYSQLND_METHOD(mysqlnd_pfc, read_compressed_packet_from_stream_and_fill_read_buffer),
489 #else
490 	NULL,
491 #endif
492 
493 	MYSQLND_METHOD(mysqlnd_pfc, free_contents),
494 MYSQLND_CLASS_METHODS_END;
495 
496 
497 /* {{{ mysqlnd_pfc_init */
498 PHPAPI MYSQLND_PFC *
mysqlnd_pfc_init(const bool persistent,MYSQLND_CLASS_METHODS_TYPE (mysqlnd_object_factory)* object_factory,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)499 mysqlnd_pfc_init(const bool persistent, MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *object_factory, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
500 {
501 	MYSQLND_CLASS_METHODS_TYPE(mysqlnd_object_factory) *factory = object_factory? object_factory : &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory);
502 	MYSQLND_PFC * pfc;
503 	DBG_ENTER("mysqlnd_pfc_init");
504 	pfc = factory->get_protocol_frame_codec(persistent, stats, error_info);
505 	DBG_RETURN(pfc);
506 }
507 /* }}} */
508 
509 
510 /* {{{ mysqlnd_pfc_free */
511 PHPAPI void
mysqlnd_pfc_free(MYSQLND_PFC * const pfc,MYSQLND_STATS * stats,MYSQLND_ERROR_INFO * error_info)512 mysqlnd_pfc_free(MYSQLND_PFC * const pfc, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info)
513 {
514 	DBG_ENTER("mysqlnd_pfc_free");
515 	if (pfc) {
516 		pfc->data->m.dtor(pfc, stats, error_info);
517 	}
518 	DBG_VOID_RETURN;
519 }
520 /* }}} */
521