/** * 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 #include #include #include #include #include #include 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 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 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 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 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(huge & 0xFF); hdr_buf[1] = static_cast((huge >> 8) & 0xFF); hdr_buf[2] = static_cast((huge >> 16) & 0xFF); hdr_buf[3] = static_cast((huge >> 24) & 0xFF); hdr_buf[4] = 1; mysqlx_write_all(fds[0], hdr_buf, 5); close(fds[0]); MysqlxFrameHeader hdr; std::vector 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 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 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 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 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 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 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 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 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(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 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(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 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 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 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(); }