mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
11 KiB
376 lines
11 KiB
/**
|
|
* mysqlx_protocol_socket_unit-t.cpp
|
|
*
|
|
* Socket-based I/O roundtrip tests for X Protocol frame handling.
|
|
*/
|
|
|
|
#include "mysqlx_protocol.h"
|
|
#include "tap.h"
|
|
|
|
#include <signal.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
int main() {
|
|
setvbuf(stdout, nullptr, _IOLBF, 0);
|
|
plan(20);
|
|
diag("=== mysqlx_protocol_socket_unit-t starting ===");
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
// --- Frame I/O roundtrip (8 tests) ---
|
|
|
|
// Test 1: write_all + read_exact roundtrip
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
uint8_t write_buf[10] = {0,1,2,3,4,5,6,7,8,9};
|
|
mysqlx_write_all(fds[0], write_buf, 10);
|
|
uint8_t read_buf[10] = {};
|
|
bool ok_read = mysqlx_read_exact(fds[1], read_buf, 10);
|
|
ok(ok_read && memcmp(write_buf, read_buf, 10) == 0,
|
|
"write_all + read_exact roundtrip preserves data");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 2: build_frame + write_all → read_frame roundtrip
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string payload = "test payload";
|
|
auto frame = mysqlx_build_frame(42, payload);
|
|
mysqlx_write_all(fds[0], frame.data(), frame.size());
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> recv_payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, recv_payload);
|
|
ok(ok_read && hdr.message_type == 42 &&
|
|
recv_payload.size() == payload.size() &&
|
|
memcmp(recv_payload.data(), payload.data(), payload.size()) == 0,
|
|
"build_frame + write_all → read_frame roundtrip matches");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 3: Two frames in FIFO order
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string p1 = "first";
|
|
std::string p2 = "second";
|
|
auto f1 = mysqlx_build_frame(1, p1);
|
|
auto f2 = mysqlx_build_frame(2, p2);
|
|
mysqlx_write_all(fds[0], f1.data(), f1.size());
|
|
mysqlx_write_all(fds[0], f2.data(), f2.size());
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok1 = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool match1 = ok1 && hdr.message_type == 1 &&
|
|
payload.size() == p1.size() &&
|
|
memcmp(payload.data(), p1.data(), p1.size()) == 0;
|
|
bool ok2 = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool match2 = ok2 && hdr.message_type == 2 &&
|
|
payload.size() == p2.size() &&
|
|
memcmp(payload.data(), p2.data(), p2.size()) == 0;
|
|
ok(match1 && match2, "two frames read in FIFO order with correct payloads");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 4: Close peer, read_frame returns false (EOF)
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
close(fds[1]);
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
ok(!mysqlx_read_frame(fds[0], hdr, payload),
|
|
"read_frame returns false after peer closed");
|
|
close(fds[0]);
|
|
}
|
|
|
|
// Test 5: Close peer, write_all returns false (EPIPE)
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
close(fds[0]);
|
|
uint8_t buf[5] = {};
|
|
ok(!mysqlx_write_all(fds[1], buf, 5),
|
|
"write_all returns false after peer closed (EPIPE)");
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 6: read_exact with len=0 returns true
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
uint8_t buf[1];
|
|
ok(mysqlx_read_exact(fds[0], buf, 0),
|
|
"read_exact with len=0 returns true immediately");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 7: Frame with payload_size=1 (just message_type)
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string empty_payload;
|
|
auto frame = mysqlx_build_frame(7, empty_payload);
|
|
mysqlx_write_all(fds[0], frame.data(), frame.size());
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, payload);
|
|
ok(ok_read && payload.empty() && hdr.message_type == 7,
|
|
"frame with payload_size=1 reads with empty payload");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 8: read_frame rejects payload_size > MYSQLX_MAX_PAYLOAD_SIZE
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
uint32_t huge = MYSQLX_MAX_PAYLOAD_SIZE + 1;
|
|
uint8_t hdr_buf[5];
|
|
hdr_buf[0] = static_cast<uint8_t>(huge & 0xFF);
|
|
hdr_buf[1] = static_cast<uint8_t>((huge >> 8) & 0xFF);
|
|
hdr_buf[2] = static_cast<uint8_t>((huge >> 16) & 0xFF);
|
|
hdr_buf[3] = static_cast<uint8_t>((huge >> 24) & 0xFF);
|
|
hdr_buf[4] = 1;
|
|
mysqlx_write_all(fds[0], hdr_buf, 5);
|
|
close(fds[0]);
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
ok(!mysqlx_read_frame(fds[1], hdr, payload),
|
|
"read_frame rejects payload_size > MYSQLX_MAX_PAYLOAD_SIZE");
|
|
close(fds[1]);
|
|
}
|
|
|
|
// --- Error/OK send+receive (6 tests) ---
|
|
|
|
// Test 9: send_error → read_frame succeeds
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
mysqlx_send_error(fds[0], 1045, "Access denied", "28000");
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
ok(mysqlx_read_frame(fds[1], hdr, payload),
|
|
"send_error → read_frame succeeds");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 10: send_ok → read_frame succeeds
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
mysqlx_send_ok(fds[0]);
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
ok(mysqlx_read_frame(fds[1], hdr, payload),
|
|
"send_ok → read_frame succeeds");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 11: send_error(1045, "Access denied") → payload non-empty
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
mysqlx_send_error(fds[0], 1045, "Access denied", "28000");
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, payload);
|
|
ok(ok_read && !payload.empty(),
|
|
"send_error(1045, \"Access denied\") → payload non-empty");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 12: send_ok with 256-char message → payload > 256
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string long_msg(256, 'A');
|
|
mysqlx_send_ok(fds[0], long_msg);
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, payload);
|
|
ok(ok_read && payload.size() > 256,
|
|
"send_ok with 256-char message → payload > 256");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 13: Multiple interleaved send_error + send_ok
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
mysqlx_send_error(fds[0], 1001, "first", "HY000");
|
|
mysqlx_send_ok(fds[0], "second");
|
|
mysqlx_send_error(fds[0], 1002, "third", "HY000");
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok1 = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool ok2 = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool ok3 = mysqlx_read_frame(fds[1], hdr, payload);
|
|
ok(ok1 && ok2 && ok3, "interleaved error/ok → all reads succeed");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 14: send_error with default sql_state
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
mysqlx_send_error(fds[0], 1045, "test error");
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
ok(mysqlx_read_frame(fds[1], hdr, payload),
|
|
"send_error with default sql_state → read succeeds");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// --- Large frames (6 tests) ---
|
|
|
|
// Test 15: 64KB payload roundtrip
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string payload_64k(64 * 1024, 'X');
|
|
auto frame = mysqlx_build_frame(1, payload_64k);
|
|
|
|
std::thread writer([fd0 = fds[0], &frame]() {
|
|
mysqlx_write_all(fd0, frame.data(), frame.size());
|
|
close(fd0);
|
|
});
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> recv_payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, recv_payload);
|
|
writer.join();
|
|
|
|
bool match = ok_read && recv_payload.size() == payload_64k.size() &&
|
|
memcmp(recv_payload.data(), payload_64k.data(), payload_64k.size()) == 0;
|
|
ok(match, "64KB payload frame roundtrip succeeds, payload matches");
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 16: 1MB payload roundtrip
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string payload_1m(1024 * 1024, 'Y');
|
|
auto frame = mysqlx_build_frame(2, payload_1m);
|
|
|
|
std::thread writer([fd0 = fds[0], &frame]() {
|
|
mysqlx_write_all(fd0, frame.data(), frame.size());
|
|
close(fd0);
|
|
});
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> recv_payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, recv_payload);
|
|
writer.join();
|
|
|
|
bool match = ok_read && recv_payload.size() == payload_1m.size() &&
|
|
memcmp(recv_payload.data(), payload_1m.data(), payload_1m.size()) == 0;
|
|
ok(match, "1MB payload frame roundtrip succeeds, payload matches");
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 17: MYSQLX_MAX_PAYLOAD_SIZE - 1 payload boundary
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string payload_boundary(static_cast<size_t>(MYSQLX_MAX_PAYLOAD_SIZE) - 1, 'Z');
|
|
auto frame = mysqlx_build_frame(3, payload_boundary);
|
|
|
|
std::thread writer([fd0 = fds[0], &frame]() {
|
|
mysqlx_write_all(fd0, frame.data(), frame.size());
|
|
close(fd0);
|
|
});
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> recv_payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, recv_payload);
|
|
writer.join();
|
|
|
|
ok(ok_read, "MYSQLX_MAX_PAYLOAD_SIZE-1 payload boundary roundtrip succeeds");
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 18: 10 small frames rapidly
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
const int N = 10;
|
|
std::string payloads[N];
|
|
for (int i = 0; i < N; i++) {
|
|
payloads[i] = "frame_" + std::to_string(i);
|
|
auto f = mysqlx_build_frame(static_cast<uint8_t>(i), payloads[i]);
|
|
mysqlx_write_all(fds[0], f.data(), f.size());
|
|
}
|
|
|
|
bool all_ok = true;
|
|
for (int i = 0; i < N; i++) {
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
if (!mysqlx_read_frame(fds[1], hdr, payload)) { all_ok = false; break; }
|
|
if (payload.size() != payloads[i].size() ||
|
|
memcmp(payload.data(), payloads[i].data(), payloads[i].size()) != 0) {
|
|
all_ok = false; break;
|
|
}
|
|
}
|
|
ok(all_ok, "10 small frames written rapidly all read correctly in order");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 19: Binary data with null bytes
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string binary_payload("\x00\x01\x02\x00\xFF\xFE", 6);
|
|
auto frame = mysqlx_build_frame(10, binary_payload);
|
|
mysqlx_write_all(fds[0], frame.data(), frame.size());
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool match = ok_read && payload.size() == binary_payload.size() &&
|
|
memcmp(payload.data(), binary_payload.data(), binary_payload.size()) == 0;
|
|
ok(match, "binary data with null bytes preserved in roundtrip");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
// Test 20: All-0xFF payload
|
|
{
|
|
int fds[2];
|
|
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
|
std::string ff_payload(256, '\xFF');
|
|
auto frame = mysqlx_build_frame(11, ff_payload);
|
|
mysqlx_write_all(fds[0], frame.data(), frame.size());
|
|
|
|
MysqlxFrameHeader hdr;
|
|
std::vector<uint8_t> payload;
|
|
bool ok_read = mysqlx_read_frame(fds[1], hdr, payload);
|
|
bool match = ok_read && payload.size() == ff_payload.size() &&
|
|
memcmp(payload.data(), ff_payload.data(), ff_payload.size()) == 0;
|
|
ok(match, "all-0xFF payload preserved in roundtrip");
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
}
|
|
|
|
return exit_status();
|
|
}
|