Skip to content

Commit 737f388

Browse files
committed
Fix handling of large compressed packets
1 parent 3afe4a6 commit 737f388

File tree

2 files changed

+69
-45
lines changed

2 files changed

+69
-45
lines changed

ext/mysqli/tests/mysqli_real_connect_compression_error.phpt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
--TEST--
22
Bug #80107 mysqli_query() fails for ~16 MB long query when compression is enabled
3-
--XFAIL--
4-
The second INSERT query fails with MySQL server has gone away
53
--SKIPIF--
64
<?php
75
require_once('skipif.inc');
@@ -13,7 +11,9 @@ mysqli.allow_local_infile=1
1311
--FILE--
1412
<?php
1513

16-
include("connect.inc");
14+
require_once("connect.inc");
15+
16+
$data_size = 16777174;
1717

1818
// Insert with compression disabled:
1919

@@ -22,7 +22,7 @@ $result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $so
2222
$mysqli->query("DROP TABLE IF EXISTS test");
2323
$mysqli->query("CREATE TABLE test (`blob` LONGBLOB NOT NULL)");
2424

25-
$data = str_repeat("x", 16777174);
25+
$data = str_repeat("x", $data_size);
2626
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");
2727

2828
var_dump(mysqli_error_list($mysqli));
@@ -33,7 +33,7 @@ $mysqli->close();
3333
$mysqli = mysqli_init();
3434
$result = my_mysqli_real_connect($mysqli, $host, $user, $passwd, $db, $port, $socket, MYSQLI_CLIENT_COMPRESS);
3535

36-
$data = str_repeat("x", 16777174);
36+
$data = str_repeat("x", $data_size);
3737
$mysqli->query("INSERT INTO $db.test(`blob`) VALUE ('$data')");
3838

3939
var_dump(mysqli_error_list($mysqli));

ext/mysqlnd/mysqlnd_protocol_frame_codec.c

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,56 @@ MYSQLND_METHOD(mysqlnd_pfc, reset)(MYSQLND_PFC * const pfc, MYSQLND_STATS * cons
4949
#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer))
5050

5151

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 "MYSQLND_SZ_T_SPEC" 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_malloc(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_free(decompressed_data);
97+
}
98+
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
99+
DBG_RETURN(bytes_sent);
100+
}
101+
52102
/* {{{ mysqlnd_pfc::send */
53103
/*
54104
IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!!
@@ -90,53 +140,27 @@ MYSQLND_METHOD(mysqlnd_pfc, send)(MYSQLND_PFC * const pfc, MYSQLND_VIO * const v
90140
DBG_INF_FMT("packet_no=%u", pfc->data->packet_no);
91141
#ifdef MYSQLND_COMPRESSION_ENABLED
92142
if (pfc->data->compressed == TRUE) {
93-
/* here we need to compress the data and then write it, first comes the compressed header */
94-
size_t tmp_complen = to_be_sent;
95-
size_t payload_size;
96143
zend_uchar * uncompressed_payload = p; /* should include the header */
97-
98144
STORE_HEADER_SIZE(safe_storage, uncompressed_payload);
99145
int3store(uncompressed_payload, to_be_sent);
100146
int1store(uncompressed_payload + 3, pfc->data->packet_no);
101-
if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen,
102-
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE))
103-
{
104-
int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE);
105-
payload_size = tmp_complen;
147+
if (to_be_sent <= MYSQLND_MAX_PACKET_SIZE - MYSQLND_HEADER_SIZE) {
148+
bytes_sent = write_compressed_packet(
149+
pfc, vio, conn_stats, error_info,
150+
uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE, compress_buf);
106151
} else {
107-
int3store(compress_buf + MYSQLND_HEADER_SIZE, 0);
108-
memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE);
109-
payload_size = to_be_sent + MYSQLND_HEADER_SIZE;
152+
/* The uncompressed size including the header would overflow. Split into two
153+
* compressed packets. The size of the first one is relatively arbitrary here. */
154+
const size_t split_off_bytes = 8192;
155+
bytes_sent = write_compressed_packet(
156+
pfc, vio, conn_stats, error_info,
157+
uncompressed_payload, split_off_bytes, compress_buf);
158+
bytes_sent = write_compressed_packet(
159+
pfc, vio, conn_stats, error_info,
160+
uncompressed_payload + split_off_bytes,
161+
to_be_sent + MYSQLND_HEADER_SIZE - split_off_bytes, compress_buf);
110162
}
111163
RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage);
112-
113-
int3store(compress_buf, payload_size);
114-
int1store(compress_buf + 3, pfc->data->packet_no);
115-
DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE);
116-
bytes_sent = vio->data->m.network_write(vio, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, conn_stats, error_info);
117-
pfc->data->compressed_envelope_packet_no++;
118-
#ifdef WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY
119-
if (res == Z_OK) {
120-
size_t decompressed_size = left + MYSQLND_HEADER_SIZE;
121-
zend_uchar * decompressed_data = mnd_malloc(decompressed_size);
122-
int error = pfc->data->m.decode(decompressed_data, decompressed_size,
123-
compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size);
124-
if (error == Z_OK) {
125-
int i;
126-
DBG_INF("success decompressing");
127-
for (i = 0 ; i < decompressed_size; i++) {
128-
if (i && (i % 30 == 0)) {
129-
printf("\n\t\t");
130-
}
131-
printf("%.2X ", (int)*((char*)&(decompressed_data[i])));
132-
DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i])));
133-
}
134-
} else {
135-
DBG_INF("error decompressing");
136-
}
137-
mnd_free(decompressed_data);
138-
}
139-
#endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */
140164
} else
141165
#endif /* MYSQLND_COMPRESSION_ENABLED */
142166
{

0 commit comments

Comments
 (0)