diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index 2a4d17f946e8..8fb2f5a4aeba 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -630,6 +630,15 @@ class my_mysqli_fake_server_conn fprintf(STDERR, "[*] Received: %s\n", bin2hex($data)); } + public function read_compressed_packet(): void + { + $header = $this->read_bytes(7); + $compressed_length = ord($header[0]) | (ord($header[1]) << 8) | (ord($header[2]) << 16); + $uncompressed_length = ord($header[4]) | (ord($header[5]) << 8) | (ord($header[6]) << 16); + $this->read_bytes($compressed_length); + fprintf(STDERR, "[*] Received compressed packet: %d bytes, uncompressed length %d\n", $compressed_length, $uncompressed_length); + } + public function close() { fclose($this->conn); @@ -1445,6 +1454,19 @@ function my_mysqli_test_stmt_response_field_len_premature(my_mysqli_fake_server_ $conn->read(65536); } +function my_mysqli_test_compressed_queries(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + $conn->send_server_ok(); + for ($i = 0; $i < 2; $i++) { + $conn->read_compressed_packet(); + $ok = $conn->packet_generator->server_ok("01")->to_bytes(); + $conn->send(substr(pack('V', strlen($ok)), 0, 3) . "\x01\x00\x00\x00" . $ok, "Compressed Server OK"); + } + $conn->read_compressed_packet(); +} + function run_fake_server(string $test_function, int|string $port = 0): void { $host = '127.0.0.1'; diff --git a/ext/mysqli/tests/mysqli_compression_threshold.phpt b/ext/mysqli/tests/mysqli_compression_threshold.phpt new file mode 100644 index 000000000000..9c679c0da883 --- /dev/null +++ b/ext/mysqli/tests/mysqli_compression_threshold.phpt @@ -0,0 +1,43 @@ +--TEST-- +Compressed protocol: packets below the threshold are sent uncompressed +--EXTENSIONS-- +mysqli +--SKIPIF-- +info(); +} +if (!str_contains(ob_get_clean(), 'Compression => supported')) { + die('skip mysqlnd is built without compression support'); +} +?> +--FILE-- +wait(); + +$link = mysqli_init(); +$link->real_connect('127.0.0.1', 'root', '', '', $process->getPort(), null, MYSQLI_CLIENT_COMPRESS); +var_dump($link->query("SELECT '" . str_repeat('a', 80) . "'")); +var_dump($link->query("SELECT '" . str_repeat('a', 1000) . "'")); +$link->close(); + +$process->terminate(true); +?> +--EXPECTF-- +[*] Server started on 127.0.0.1:%d +[*] Connection established +[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264 +[*] Received: %s +[*] Sending - Server OK: 0700000200000002000000 +[*] Received compressed packet: 94 bytes, uncompressed length 0 +[*] Sending - Compressed Server OK: 0b0000010000000700000100000002000000 +bool(true) +[*] Received compressed packet: %d bytes, uncompressed length 1014 +[*] Sending - Compressed Server OK: 0b0000010000000700000100000002000000 +bool(true) +[*] Received compressed packet: 5 bytes, uncompressed length 0 +[*] Server finished diff --git a/ext/mysqlnd/mysqlnd_protocol_frame_codec.c b/ext/mysqlnd/mysqlnd_protocol_frame_codec.c index 70a81befdd71..2663a101ed0a 100644 --- a/ext/mysqlnd/mysqlnd_protocol_frame_codec.c +++ b/ext/mysqlnd/mysqlnd_protocol_frame_codec.c @@ -55,7 +55,8 @@ static ssize_t write_compressed_packet( /* here we need to compress the data and then write it, first comes the compressed header */ size_t tmp_complen = to_be_sent; size_t payload_size; - if (PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen, + if (to_be_sent >= MYSQLND_MIN_COMPRESS_LENGTH + && PASS == pfc->data->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen, uncompressed_payload, to_be_sent)) { int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent); diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index ad452ec4f928..38bb4ce3c0bf 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -18,6 +18,8 @@ #define MYSQLND_HEADER_SIZE 4 #define COMPRESSED_HEADER_SIZE 3 +/* Packets smaller than this are sent uncompressed: they barely shrink. */ +#define MYSQLND_MIN_COMPRESS_LENGTH 100 #define MYSQLND_NULL_LENGTH (zend_ulong) ~0