/**
 * @file pgsql-test_malformed_packet-t.cpp
 * @brief Validates ProxySQL's PostgreSQL protocol handling stability and ensures it does not crash
 *        when subjected to multiple malformed packets on its admin and backend connections.
 *
 * This test has two phases:
 *
 * Phase 1: Malformed packets BEFORE authentication
 * - Sends various malformed PostgreSQL protocol packets prior to completing authentication
 * - Verifies that ProxySQL properly rejects malformed packets without crashing
 *
 * Phase 2: Malformed packets AFTER authentication
 * - Establishes authenticated connections to BACKEND and ADMIN interfaces
 * - Sends malformed packets (Query, Parse, Bind, Execute, etc.) after successful authentication
 * - Verifies that ProxySQL handles malformed data gracefully and remains operational
 *
 * Verification criteria for both phases:
 * 1. ProxySQL does not crash when receiving malformed packets
 * 2. ProxySQL properly closes the connection or sends error responses
 * 3. ProxySQL remains operational for legitimate connections after test completion
 *
 * Test cases cover:
 * - Truncated startup packets
 * - Invalid length fields (zero, too large, mismatched, integer overflow)
 * - Malformed SSL requests and cancel requests
 * - Invalid packet types before startup
 * - Malformed SASL authentication data
 * - Query packets with missing null terminators (Phase 2)
 * - Extended query protocol malformed packets (Parse, Bind, Execute, Close, Describe) (Phase 2)
 * - Copy protocol malformed packets (Phase 2)
 */

#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <fcntl.h>
#include <cstring>
#include <vector>
#include <string>
#include <sstream>

#include "libpq-fe.h"
#include "tap.h"
#include "command_line.h"
#include "utils.h"

constexpr size_t BUFFER_SIZE = 4096;
constexpr int TIMEOUT_SEC = 5;

// PostgreSQL protocol constants
constexpr uint32_t PG_PROTOCOL_VERSION = 196608;  // 3.0 (0x00030000)
constexpr uint32_t PG_SSL_REQUEST_CODE = 80877103;  // (1234 << 16) | 5679
constexpr uint32_t PG_CANCEL_REQUEST_CODE = 80877102;  // (1234 << 16) | 5678
constexpr uint32_t PG_GSS_ENCRYPT_CODE = 80877104;  // (1234 << 16) | 5680

// Forward declarations for helper functions
bool send_exact(int sock, const void* buf, size_t len);
bool recv_exact(int sock, void* buf, size_t len);

#define REPORT_ERROR_AND_EXIT(fmt, ...) \
    do { \
        fprintf(stderr, "File %s, line %d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
        close(sock); \
        return; \
    } while (0)

typedef enum {
    BACKEND = 0,
    ADMIN
} Connection_type_t;

/**
 * @brief Create a raw TCP socket connection to the specified host and port
 *
 * Supports both IP addresses (IPv4 and IPv6) and domain names.
 * Uses getaddrinfo() for name resolution.
 */
int create_raw_connection(const std::string& host, int port) {
    struct addrinfo hints{};
    struct addrinfo* result = nullptr;
    struct addrinfo* rp = nullptr;
    int sock = -1;

    // Prepare hints for getaddrinfo
    hints.ai_family = AF_UNSPEC;      // Allow IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM;  // TCP socket

    // Convert port to string for getaddrinfo
    std::string port_str = std::to_string(port);

    // Resolve the hostname
    int gai_err = getaddrinfo(host.c_str(), port_str.c_str(), &hints, &result);
    if (gai_err != 0) {
        fprintf(stderr, "Failed to resolve host '%s': %s\n",
                host.c_str(), gai_strerror(gai_err));
        return -1;
    }

    // Try each address returned by getaddrinfo until we successfully connect
    for (rp = result; rp != nullptr; rp = rp->ai_next) {
        sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sock < 0) {
            continue;  // Try next address
        }

        // Set socket timeout
        struct timeval timeout;
        timeout.tv_sec = TIMEOUT_SEC;
        timeout.tv_usec = 0;
        if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
            fprintf(stderr, "Failed to set socket timeout: %s\n", strerror(errno));
            close(sock);
            sock = -1;
            continue;
        }

        // Set TCP_NODELAY for immediate packet sending
        int flag = 1;
        setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

        // Attempt to connect
        if (connect(sock, rp->ai_addr, rp->ai_addrlen) == 0) {
            // Success!
            break;
        }

        // Connection failed, try next address
        close(sock);
        sock = -1;
    }

    freeaddrinfo(result);

    if (sock < 0) {
        fprintf(stderr, "Connection failed to %s:%d\n", host.c_str(), port);
        return -1;
    }

    return sock;
}

/**
 * @brief Send a malformed packet and verify ProxySQL handles it gracefully
 *
 * Valid outcomes:
 * - Connection closed (bytes_received <= 0): ProxySQL rejected the packet immediately
 * - Error response received (bytes_received > 0): ProxySQL sent an error packet (also valid)
 * - Both are acceptable - what matters is ProxySQL doesn't crash
 */
void test_malformed_packet(const std::string& test_name,
                          const std::string& host,
                          int port,
                          const std::vector<uint8_t>& data) {

    int sock = create_raw_connection(host, port);
    if (sock < 0) {
        ok(0, "%s: Failed to create connection", test_name.c_str());
        return;
    }

    // Send the malformed data
    if (!send_exact(sock, data.data(), data.size())) {
        close(sock);
        ok(0, "%s: Failed to send data", test_name.c_str());
        return;
    }

    // Try to receive response
    std::vector<char> buffer(BUFFER_SIZE);
    ssize_t bytes_received = recv(sock, buffer.data(), buffer.size(), 0);

    // Valid outcomes:
    // - Connection closed (bytes_received == 0): ProxySQL rejected/closed connection
    // - Timeout (bytes_received < 0 with EAGAIN/EWOULDBLOCK): No response within timeout
    // - Error response (bytes_received > 0 && buffer[0] == 'E'): ProxySQL sent error
    bool connection_closed = (bytes_received == 0);
    bool timeout = (bytes_received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
    bool got_error_response = (bytes_received > 0 && buffer[0] == 'E');
    bool handled_gracefully = connection_closed || timeout || got_error_response;

    ok(handled_gracefully, "%s: Malformed packet handled (closed=%d, timeout=%d, error=%d)",
       test_name.c_str(), (int)connection_closed, (int)timeout, (int)got_error_response);

    close(sock);
}

/**
 * @brief Verify ProxySQL is still operational after malformed packet test
 */
bool verify_proxysql_alive(const CommandLine& cl, Connection_type_t conn_type) {
    std::stringstream ss;
    if (conn_type == BACKEND) {
        ss << "host=" << cl.pgsql_host << " port=" << cl.pgsql_port;
        ss << " user=" << cl.pgsql_username << " password=" << cl.pgsql_password;
    } else {
        // Admin interface - use MySQL admin credentials on PostgreSQL admin port
        ss << "host=" << cl.admin_host << " port=" << cl.pgsql_admin_port;
        ss << " user=" << cl.admin_username << " password=" << cl.admin_password;
    }
    ss << " sslmode=disable";

    PGconn* conn = PQconnectdb(ss.str().c_str());
    if (PQstatus(conn) != CONNECTION_OK) {
        PQfinish(conn);
        return false;
    }

    // Try a simple query
    PGresult* res = PQexec(conn, "SELECT 1");
    bool success = (PQresultStatus(res) == PGRES_TUPLES_OK);
    PQclear(res);
    PQfinish(conn);

    return success;
}

/**
 * @brief Build a valid startup packet
 */
std::vector<uint8_t> build_startup_packet(const std::vector<std::pair<std::string, std::string>>& params) {
    std::vector<uint8_t> packet;

    // Reserve space for length (4 bytes)
    packet.resize(4);

    // Protocol version (4 bytes, big-endian)
    packet.push_back((PG_PROTOCOL_VERSION >> 24) & 0xFF);
    packet.push_back((PG_PROTOCOL_VERSION >> 16) & 0xFF);
    packet.push_back((PG_PROTOCOL_VERSION >> 8) & 0xFF);
    packet.push_back(PG_PROTOCOL_VERSION & 0xFF);

    // Parameters (name\0value\0)
    for (const auto& [name, value] : params) {
        for (char c : name) packet.push_back(c);
        packet.push_back(0);
        for (char c : value) packet.push_back(c);
        packet.push_back(0);
    }

    // Null terminator
    packet.push_back(0);

    // Update length
    uint32_t len = packet.size();
    packet[0] = (len >> 24) & 0xFF;
    packet[1] = (len >> 16) & 0xFF;
    packet[2] = (len >> 8) & 0xFF;
    packet[3] = len & 0xFF;

    return packet;
}

/**
 * @brief Build SSL request packet
 */
std::vector<uint8_t> build_ssl_request_packet() {
    std::vector<uint8_t> packet(8);

    // Length
    packet[0] = 0;
    packet[1] = 0;
    packet[2] = 0;
    packet[3] = 8;

    // SSL request code
    packet[4] = (PG_SSL_REQUEST_CODE >> 24) & 0xFF;
    packet[5] = (PG_SSL_REQUEST_CODE >> 16) & 0xFF;
    packet[6] = (PG_SSL_REQUEST_CODE >> 8) & 0xFF;
    packet[7] = PG_SSL_REQUEST_CODE & 0xFF;

    return packet;
}

/**
 * @brief Build cancel request packet
 */
std::vector<uint8_t> build_cancel_request_packet(uint32_t pid, uint32_t key) {
    std::vector<uint8_t> packet(16);

    // Length
    packet[0] = 0;
    packet[1] = 0;
    packet[2] = 0;
    packet[3] = 16;

    // Cancel request code
    packet[4] = (PG_CANCEL_REQUEST_CODE >> 24) & 0xFF;
    packet[5] = (PG_CANCEL_REQUEST_CODE >> 16) & 0xFF;
    packet[6] = (PG_CANCEL_REQUEST_CODE >> 8) & 0xFF;
    packet[7] = PG_CANCEL_REQUEST_CODE & 0xFF;

    // Process ID
    packet[8] = (pid >> 24) & 0xFF;
    packet[9] = (pid >> 16) & 0xFF;
    packet[10] = (pid >> 8) & 0xFF;
    packet[11] = pid & 0xFF;

    // Secret key
    packet[12] = (key >> 24) & 0xFF;
    packet[13] = (key >> 16) & 0xFF;
    packet[14] = (key >> 8) & 0xFF;
    packet[15] = key & 0xFF;

    return packet;
}

/**
 * @brief Build password message packet (type 'p')
 */
std::vector<uint8_t> build_password_packet(const std::string& password) {
    std::vector<uint8_t> packet;

    // Type byte
    packet.push_back('p');

    // Reserve space for length
    packet.resize(5);

    // Password (null-terminated)
    for (char c : password) packet.push_back(c);
    packet.push_back(0);

    // Update length (includes length field itself)
    uint32_t len = packet.size() - 1;  // Exclude type byte from length
    packet[1] = (len >> 24) & 0xFF;
    packet[2] = (len >> 16) & 0xFF;
    packet[3] = (len >> 8) & 0xFF;
    packet[4] = len & 0xFF;

    return packet;
}

void run_malformed_packet_tests(const std::string& host, int port, const char* target_name) {
    diag(">>> Testing malformed packets on %s (%s:%d) <<<", target_name, host.c_str(), port);

    // ==================== STARTUP PACKET TESTS ====================

    // Test 1: Empty packet
    test_malformed_packet("Empty packet", host, port, {});

    // Test 2: Truncated startup - only length, no version
    test_malformed_packet("Truncated startup (length only)", host, port,
        {0x00, 0x00, 0x00, 0x08});

    // Test 3: Startup with length = 0
    test_malformed_packet("Startup with zero length", host, port,
        {0x00, 0x00, 0x00, 0x00});

    // Test 4: Startup with length = 4 (too small, only version, no params)
    test_malformed_packet("Startup with minimal length", host, port,
        {0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00});

    // Test 5: Startup with invalid protocol version (0)
    test_malformed_packet("Startup with version 0", host, port,
        {0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00});

    // Test 6: Startup with invalid protocol version (1)
    test_malformed_packet("Startup with version 1", host, port,
        {0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01});

    // Test 7: Startup with huge length (potential DoS)
    test_malformed_packet("Startup with huge length", host, port,
        {0x7F, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0x00, 0x00});

    // Test 8: Startup with length > packet size (mismatch)
    test_malformed_packet("Startup length > data", host, port,
        {0x00, 0x00, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00});

    // Test 9: Startup packet without null terminator
    test_malformed_packet("Startup without terminator", host, port,
        {0x00, 0x00, 0x00, 0x0C, 0x00, 0x03, 0x00, 0x00,
         'u', 's', 'e', 'r'});

    // Test 10: Startup with user param but no value
    test_malformed_packet("Startup with user, no value", host, port,
        {0x00, 0x00, 0x00, 0x0D, 0x00, 0x03, 0x00, 0x00,
         'u', 's', 'e', 'r', 0x00});

    // Test 11: Startup with only version and null terminator
    test_malformed_packet("Startup with only version", host, port,
        {0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00});

    // ==================== SSL REQUEST TESTS ====================

    // Test 12: SSL request with wrong length (too short)
    test_malformed_packet("SSL request with short length", host, port,
        {0x00, 0x00, 0x00, 0x04,
         (PG_SSL_REQUEST_CODE >> 24) & 0xFF,
         (PG_SSL_REQUEST_CODE >> 16) & 0xFF,
         (PG_SSL_REQUEST_CODE >> 8) & 0xFF,
         PG_SSL_REQUEST_CODE & 0xFF});

    // Test 13: SSL request with wrong length (too long)
    test_malformed_packet("SSL request with long length", host, port,
        {0x00, 0x00, 0x00, 0x10,
         (PG_SSL_REQUEST_CODE >> 24) & 0xFF,
         (PG_SSL_REQUEST_CODE >> 16) & 0xFF,
         (PG_SSL_REQUEST_CODE >> 8) & 0xFF,
         PG_SSL_REQUEST_CODE & 0xFF});

    // Test 14: SSL request with invalid code
    test_malformed_packet("SSL request with invalid code", host, port,
        {0x00, 0x00, 0x00, 0x08,
         0xDE, 0xAD, 0xBE, 0xEF});

    // Test 15: GSS encrypt request (not supported)
    test_malformed_packet("GSS encrypt request", host, port,
        {0x00, 0x00, 0x00, 0x08,
         (PG_GSS_ENCRYPT_CODE >> 24) & 0xFF,
         (PG_GSS_ENCRYPT_CODE >> 16) & 0xFF,
         (PG_GSS_ENCRYPT_CODE >> 8) & 0xFF,
         PG_GSS_ENCRYPT_CODE & 0xFF});

    // ==================== CANCEL REQUEST TESTS ====================

    // Test 16: Cancel request with length too short (< 16)
    test_malformed_packet("Cancel request too short", host, port,
        {0x00, 0x00, 0x00, 0x0C,
         (PG_CANCEL_REQUEST_CODE >> 24) & 0xFF,
         (PG_CANCEL_REQUEST_CODE >> 16) & 0xFF,
         (PG_CANCEL_REQUEST_CODE >> 8) & 0xFF,
         PG_CANCEL_REQUEST_CODE & 0xFF,
         0x00, 0x00, 0x00, 0x01});

    // Test 17: Cancel request with length too long (> 268)
    test_malformed_packet("Cancel request too long", host, port,
        {0x00, 0x00, 0x01, 0x0D,  // 269 bytes
         (PG_CANCEL_REQUEST_CODE >> 24) & 0xFF,
         (PG_CANCEL_REQUEST_CODE >> 16) & 0xFF,
         (PG_CANCEL_REQUEST_CODE >> 8) & 0xFF,
         PG_CANCEL_REQUEST_CODE & 0xFF});

    // Test 18: Cancel request with invalid code
    test_malformed_packet("Cancel request invalid code", host, port,
        {0x00, 0x00, 0x00, 0x10,
         0xDE, 0xAD, 0xBE, 0xEF,
         0x00, 0x00, 0x00, 0x01,
         0x00, 0x00, 0x00, 0x02});

    // ==================== PACKET TYPE CONFUSION TESTS ====================

    // Test 19: Regular packet type before startup
    test_malformed_packet("Password packet before startup", host, port,
        build_password_packet("test"));

    // Test 20: Query packet before startup
    test_malformed_packet("Query packet before startup", host, port,
        {'Q', 0x00, 0x00, 0x00, 0x0D, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1', 0x00});

    // Test 21: Unknown packet type
    test_malformed_packet("Unknown packet type (0xFF)", host, port,
        {0xFF, 0x00, 0x00, 0x00, 0x04});

    // Test 22: Packet with length overflow (negative when cast to signed)
    test_malformed_packet("Packet with length 0x80000000", host, port,
        {'Q', 0x80, 0x00, 0x00, 0x00});

    // Test 23: Special packet with non-zero second byte
    test_malformed_packet("Special packet with non-zero second byte", host, port,
        {0x00, 0x01, 0x00, 0x00, 0x00, 0x08});

    // ==================== MALFORMED DATA TESTS ====================

    // Test 24: Random binary data
    test_malformed_packet("Random binary data", host, port,
        {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
         0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0});

    // Test 25: All zeros
    test_malformed_packet("All zeros packet", host, port,
        std::vector<uint8_t>(100, 0x00));

    // Test 26: All 0xFF
    test_malformed_packet("All 0xFF packet", host, port,
        std::vector<uint8_t>(100, 0xFF));

    // Test 27: Maximum length field with minimal data
    test_malformed_packet("Max length, min data", host, port,
        {'Q', 0xFF, 0xFF, 0xFF, 0xFF});

    // Test 28: Startup v2 protocol (deprecated)
    test_malformed_packet("Startup v2 protocol", host, port,
        {0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00});
}

/**
 * @brief Build a PostgreSQL message packet with type byte
 */
std::vector<uint8_t> build_message_packet(char type, const std::vector<uint8_t>& data) {
    std::vector<uint8_t> packet;
    packet.reserve(1 + 4 + data.size());

    // Message type
    packet.push_back(type);

    // Message length (includes self)
    uint32_t len = data.size() + 4;
    packet.push_back((len >> 24) & 0xFF);
    packet.push_back((len >> 16) & 0xFF);
    packet.push_back((len >> 8) & 0xFF);
    packet.push_back(len & 0xFF);

    // Message data
    packet.insert(packet.end(), data.begin(), data.end());

    return packet;
}

/**
 * @brief Read an exact number of bytes from socket
 * @return true if all bytes received, false on error/short read
 */
bool recv_exact(int sock, void* buf, size_t len) {
    char* ptr = (char*)buf;
    size_t received = 0;
    while (received < len) {
        ssize_t n = recv(sock, ptr + received, len - received, 0);
        if (n <= 0) return false;
        received += n;
    }
    return true;
}

/**
 * @brief Send an exact number of bytes to socket
 * @return true if all bytes sent, false on error/short write
 */
bool send_exact(int sock, const void* buf, size_t len) {
    const char* ptr = (const char*)buf;
    size_t sent = 0;
    while (sent < len) {
        ssize_t n = send(sock, ptr + sent, len - sent, 0);
        if (n <= 0) return false;
        sent += n;
    }
    return true;
}

/**
 * @brief Read a PostgreSQL message from socket
 */
bool read_message(int sock, char& type, std::vector<uint8_t>& buffer) {
    // Read message type (1 byte)
    char type_buf[1];
    if (!recv_exact(sock, type_buf, 1)) return false;
    type = type_buf[0];

    // Read length (4 bytes, big-endian, includes self)
    char len_buf[4];
    if (!recv_exact(sock, len_buf, 4)) return false;

    int32_t len = ((unsigned char)len_buf[0] << 24) |
                  ((unsigned char)len_buf[1] << 16) |
                  ((unsigned char)len_buf[2] << 8) |
                  (unsigned char)len_buf[3];

    if (len < 4) return false;

    // Read message body
    int32_t body_len = len - 4;
    buffer.resize(body_len);
    if (!recv_exact(sock, buffer.data(), body_len)) return false;

    return true;
}

/**
 * @brief Send a malformed packet on an authenticated connection (Phase 2)
 *
 * Phase 2 tests send malformed packets AFTER successful authentication.
 * This tests post-authentication crash vulnerabilities.
 */
void test_malformed_packet_phase2(const std::string& test_name,
                                   const std::string& host,
                                   int port,
                                   const std::string& username,
                                   const std::string& password,
                                   const std::vector<uint8_t>& malformed_data) {

    // Use libpq to establish authenticated connection, then extract socket
    std::stringstream conninfo;
    conninfo << "host=" << host << " port=" << port
             << " user=" << username << " password=" << password
             << " sslmode=disable";

    PGconn* conn = PQconnectdb(conninfo.str().c_str());
    if (PQstatus(conn) != CONNECTION_OK) {
        diag("libpq connection failed: %s", PQerrorMessage(conn));
        PQfinish(conn);
        ok(0, "%s: Failed to create libpq connection", test_name.c_str());
        return;
    }

    // Get the underlying socket from libpq connection
    int sock = PQsocket(conn);
    if (sock < 0) {
        diag("PQsocket failed");
        PQfinish(conn);
        ok(0, "%s: Failed to get socket from libpq connection", test_name.c_str());
        return;
    }

    // Set socket to blocking mode (libpq may have set it non-blocking)
    int flags = fcntl(sock, F_GETFL, 0);
    if (flags >= 0) {
        fcntl(sock, F_SETFL, flags & ~O_NONBLOCK);
    }

    // Note: libpq has already completed authentication and reached ReadyForQuery
    // We can now send malformed packets on this authenticated connection

    // Step 6: Send the malformed packet on the authenticated connection
    if (!send_exact(sock, malformed_data.data(), malformed_data.size())) {
        close(sock);
        ok(0, "%s: Failed to send malformed data", test_name.c_str());
        return;
    }

    // Step 7: Wait for response to the malformed packet
    // Keep reading messages until we get an error response, connection close, or timeout
    bool got_error_response = false;
    bool connection_closed = false;
    bool timeout = false;
    char first_byte = 0;

    for (int i = 0; i < 16; ++i) {
        char msg_type = 0;
        std::vector<uint8_t> msg_data;

        // Try to read a message with timeout
        ssize_t bytes_received = recv(sock, &msg_type, 1, MSG_PEEK | MSG_DONTWAIT);
        if (bytes_received == 0) {
            connection_closed = true;
            break;
        }
        if (bytes_received < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                timeout = true;
            }
            break;
        }

        // Message available, read it fully
        if (!read_message(sock, msg_type, msg_data)) {
            connection_closed = true;
            break;
        }

        first_byte = msg_type;

        // Check if this is an error response to our malformed packet
        if (msg_type == 'E') {
            got_error_response = true;
            break;
        }
        // Continue reading if it's other protocol traffic
    }

    bool handled_gracefully = connection_closed || timeout || got_error_response;

    ok(handled_gracefully, "%s: Phase 2 malformed packet handled (closed=%d, timeout=%d, error=%d, first=0x%02X)",
       test_name.c_str(), (int)connection_closed, (int)timeout, (int)got_error_response,
       first_byte);

    // Clean up libpq connection (closes socket internally)
    PQfinish(conn);
}

/**
 * @brief Run Phase 2 tests - malformed packets AFTER authentication
 *
 * Phase 2 tests establish a valid connection first, then send malformed
 * packets on the authenticated connection to test post-auth crash vectors.
 */
void run_phase2_malformed_packet_tests(const std::string& host, int port,
                                        const std::string& username,
                                        const std::string& password,
                                        const char* target_name) {
    diag(">>> Phase 2: Testing malformed packets AFTER AUTH on %s (%s:%d) <<<",
         target_name, host.c_str(), port);

    // ==================== QUERY PACKET TESTS ('Q') ====================

    // Test 1: Query packet with length < 5 (integer underflow)
    // This targets PgSQL_Session.cpp:2079-2082 where query_len = pkt.size - 5
    test_malformed_packet_phase2("Query packet length=4 (underflow)",
        host, port, username, password,
        {'Q', 0x00, 0x00, 0x00, 0x04});

    // Test 2: Query packet with length=0
    test_malformed_packet_phase2("Query packet length=0",
        host, port, username, password,
        {'Q', 0x00, 0x00, 0x00, 0x00});

    // Test 3: Query packet with length=1 (just type byte)
    test_malformed_packet_phase2("Query packet length=1",
        host, port, username, password,
        {'Q', 0x00, 0x00, 0x00, 0x01});

    // Test 4: Query with null terminator but truncated
    test_malformed_packet_phase2("Query truncated without null",
        host, port, username, password,
        {'Q', 0x00, 0x00, 0x00, 0x08, 'S', 'E', 'L', 'E'});

    // Test 5: Query with huge length
    test_malformed_packet_phase2("Query with huge length",
        host, port, username, password,
        {'Q', 0x7F, 0xFF, 0xFF, 0xFF});

    // Test 6: Query with negative length (0x80000000)
    test_malformed_packet_phase2("Query with negative length",
        host, port, username, password,
        {'Q', 0x80, 0x00, 0x00, 0x00});

    // ==================== EXTENDED QUERY PROTOCOL TESTS ====================

    // Test 7: Parse 'P' packet with length < 5
    test_malformed_packet_phase2("Parse packet length=4",
        host, port, username, password,
        {'P', 0x00, 0x00, 0x00, 0x04});

    // Test 8: Parse packet with statement name no null terminator
    test_malformed_packet_phase2("Parse stmt name no null",
        host, port, username, password,
        {'P', 0x00, 0x00, 0x00, 0x0A, 's', 't', 'm', 't', '1', 0x00});

    // Test 9: Parse packet with query no null terminator
    test_malformed_packet_phase2("Parse query no null",
        host, port, username, password,
        {'P', 0x00, 0x00, 0x00, 0x10,
         's', 0x00,
         'S', 'E', 'L', 'E', 'C', 'T', ' ', '1', 0x00});

    // Test 10: Bind 'B' packet with length < 5
    test_malformed_packet_phase2("Bind packet length=4",
        host, port, username, password,
        {'B', 0x00, 0x00, 0x00, 0x04});

    // Test 11: Bind with malformed parameter format codes
    test_malformed_packet_phase2("Bind malformed param formats",
        host, port, username, password,
        {'B', 0x00, 0x00, 0x00, 0x0C,
         'p', 0x00,
         's', 0x00,
         0x00, 0x01,
         0x00, 0x01});

    // Test 12: Execute 'E' packet with length < 5
    test_malformed_packet_phase2("Execute packet length=4",
        host, port, username, password,
        {'E', 0x00, 0x00, 0x00, 0x04});

    // Test 13: Execute with portal name no null
    test_malformed_packet_phase2("Execute portal no null",
        host, port, username, password,
        {'E', 0x00, 0x00, 0x00, 0x08,
         'p', 'o', 'r', 't', 'a', 'l', '1'});

    // Test 14: Close 'C' packet with length < 5
    test_malformed_packet_phase2("Close packet length=4",
        host, port, username, password,
        {'C', 0x00, 0x00, 0x00, 0x04});

    // Test 15: Describe 'D' packet with length < 5
    test_malformed_packet_phase2("Describe packet length=4",
        host, port, username, password,
        {'D', 0x00, 0x00, 0x00, 0x04});

    // Test 16: Sync 'S' packet with invalid length
    test_malformed_packet_phase2("Sync packet invalid length",
        host, port, username, password,
        {'S', 0x00, 0x00, 0x00, 0x08});

    // Test 17: Flush 'H' packet with invalid length
    test_malformed_packet_phase2("Flush packet invalid length",
        host, port, username, password,
        {'H', 0x00, 0x00, 0x00, 0x08});

    // ==================== COPY PROTOCOL TESTS ====================

    // Test 18: CopyData 'd' packet with zero length
    test_malformed_packet_phase2("CopyData length=4",
        host, port, username, password,
        {'d', 0x00, 0x00, 0x00, 0x04});

    // Test 19: CopyData with huge length
    test_malformed_packet_phase2("CopyData huge length",
        host, port, username, password,
        {'d', 0x7F, 0xFF, 0xFF, 0xFF});

    // Test 20: CopyDone 'c' packet with invalid length
    test_malformed_packet_phase2("CopyDone invalid length",
        host, port, username, password,
        {'c', 0x00, 0x00, 0x00, 0x08});

    // Test 21: CopyFail 'f' packet with no error message
    test_malformed_packet_phase2("CopyFail no message",
        host, port, username, password,
        {'f', 0x00, 0x00, 0x00, 0x04});

    // ==================== TERMINATE MESSAGE TESTS ====================

    // Test 22: Terminate 'X' packet with invalid length
    test_malformed_packet_phase2("Terminate invalid length",
        host, port, username, password,
        {'X', 0xFF, 0xFF, 0xFF, 0xFF});

    // Test 23: Terminate with length=0
    test_malformed_packet_phase2("Terminate length=0",
        host, port, username, password,
        {'X', 0x00, 0x00, 0x00, 0x00});

    // ==================== MULTI-PACKET / ARITHMETIC TESTS ====================

    // Test 24: Multiple small packets in sequence
    std::vector<uint8_t> multi_pkt;
    for (int i = 0; i < 10; i++) {
        multi_pkt.push_back('Q');
        multi_pkt.push_back(0x00);
        multi_pkt.push_back(0x00);
        multi_pkt.push_back(0x00);
        multi_pkt.push_back(0x05);
        multi_pkt.push_back('x');
    }
    test_malformed_packet_phase2("Multiple small queries",
        host, port, username, password, multi_pkt);

    // Test 25: Rapid fire empty packets
    std::vector<uint8_t> rapid;
    for (int i = 0; i < 100; i++) {
        rapid.insert(rapid.end(), {'S', 0x00, 0x00, 0x00, 0x04});
    }
    test_malformed_packet_phase2("Rapid fire sync packets",
        host, port, username, password, rapid);

    // ==================== DATA TYPE OVERFLOW TESTS ====================

    // Test 26: Integer overflow in length field
    test_malformed_packet_phase2("Integer overflow length",
        host, port, username, password,
        {'Q', 0xFF, 0xFF, 0xFF, 0xFF});

    // Test 27: Parameter count overflow in Bind
    test_malformed_packet_phase2("Bind param count overflow",
        host, port, username, password,
        {'B', 0x00, 0x00, 0x00, 0x20,
         'p', 0x00,
         's', 0x00,
         0xFF, 0xFF,
         0x00, 0x00});

    // Test 28: Column count overflow in Parse
    test_malformed_packet_phase2("Parse column count overflow",
        host, port, username, password,
        {'P', 0x00, 0x00, 0x00, 0x10,
         's', 0x00,
         'Q', 0x00,
         0xFF, 0xFF,
         0x00, 0x00});

    // ==================== PASSWORD MESSAGE TESTS ====================
    // Tests for extract_password() vulnerability

    // Test 29: Password message with length=4 (too small)
    test_malformed_packet_phase2("Password message length=4",
        host, port, username, password,
        {'p', 0x00, 0x00, 0x00, 0x04});

    // Test 30: Password message with huge length field
    test_malformed_packet_phase2("Password message huge length",
        host, port, username, password,
        {'p', 0x7F, 0xFF, 0xFF, 0xFF});

    // Test 31: Password message with length=0
    test_malformed_packet_phase2("Password message length=0",
        host, port, username, password,
        {'p', 0x00, 0x00, 0x00, 0x00});

    // ==================== SASL AUTHENTICATION TESTS ====================
    // Tests for scram_handle_client_first/client_final vulnerabilities

    // Test 32: SASL client-first with invalid length
    test_malformed_packet_phase2("SASL client-first invalid length",
        host, port, username, password,
        {'p', 0x00, 0x00, 0x00, 0x08,
         'S', 'C', 'R', 'A', 'M', '-', 'S', 'H'});

    // Test 33: SASL client-first with truncated data
    test_malformed_packet_phase2("SASL client-first truncated",
        host, port, username, password,
        {'p', 0x00, 0x00, 0x01, 0x00,
         'S', 'C', 'R', 'A', 'M', '-', 'S', 'H', 'A', '-', '2', '5', '6', 0x00});

    // Test 34: SASL response with invalid length
    test_malformed_packet_phase2("SASL response invalid length",
        host, port, username, password,
        {'p', 0xFF, 0xFF, 0xFF, 0xFF});

    // ==================== MULTI-PACKET TESTS ====================
    // Tests for multi_pkt underflow vulnerability

    // Test 35: Multi-packet scenario with small size
    test_malformed_packet_phase2("Multi-packet small size",
        host, port, username, password,
        {'Q', 0x00, 0x00, 0x00, 0x05, 'x'});

    // ==================== EXTENDED QUERY LENGTH TESTS ====================
    // Additional tests for extended query protocol

    // Test 36: Parse with zero-length statement name and query
    test_malformed_packet_phase2("Parse zero length fields",
        host, port, username, password,
        {'P', 0x00, 0x00, 0x00, 0x06, 0x00, 0x00});

    // Test 37: Bind with zero-length portal and statement
    test_malformed_packet_phase2("Bind zero length fields",
        host, port, username, password,
        {'B', 0x00, 0x00, 0x00, 0x06, 0x00, 0x00});

    // Test 38: Execute with zero-length portal
    test_malformed_packet_phase2("Execute zero length portal",
        host, port, username, password,
        {'E', 0x00, 0x00, 0x00, 0x05, 0x00});

    // Test 39: Close with zero-length name
    test_malformed_packet_phase2("Close zero length name",
        host, port, username, password,
        {'C', 0x00, 0x00, 0x00, 0x05, 0x00});

    // Test 40: Describe with zero-length name
    test_malformed_packet_phase2("Describe zero length name",
        host, port, username, password,
        {'D', 0x00, 0x00, 0x00, 0x05, 0x00});

    // ==================== PACKET TYPE CONFUSION AFTER AUTH ====================

    // Test 41: Startup packet sent after authentication
    test_malformed_packet_phase2("Startup after auth",
        host, port, username, password,
        {0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, 0x00});

    // Test 42: SSL request after authentication
    test_malformed_packet_phase2("SSL request after auth",
        host, port, username, password,
        {0x00, 0x00, 0x00, 0x08,
         0x04, 0xD2, 0x16, 0x2F});

    // Test 43: Cancel request after authentication
    test_malformed_packet_phase2("Cancel request after auth",
        host, port, username, password,
        {0x00, 0x00, 0x00, 0x10,
         0x04, 0xD2, 0x16, 0x2E,
         0x00, 0x00, 0x00, 0x01,
         0x00, 0x00, 0x00, 0x02});
}

/**
 * @brief Final comprehensive check to verify ProxySQL did not crash
 * This runs after ALL tests and reports whether ProxySQL is still alive
 */
bool final_crash_check(const CommandLine& cl) {
    diag(">>> Final crash verification - checking if ProxySQL survived all malformed packets <<<");

    // Try to connect to BACKEND
    std::stringstream ss_backend;
    ss_backend << "host=" << cl.pgsql_host << " port=" << cl.pgsql_port;
    ss_backend << " user=" << cl.pgsql_username << " password=" << cl.pgsql_password;
    ss_backend << " sslmode=disable";

    PGconn* conn = PQconnectdb(ss_backend.str().c_str());
    if (PQstatus(conn) != CONNECTION_OK) {
        diag("CRASH DETECTED: Cannot connect to BACKEND: %s", PQerrorMessage(conn));
        PQfinish(conn);
        return false;
    }

    PGresult* res = PQexec(conn, "SELECT 1");
    bool backend_ok = (PQresultStatus(res) == PGRES_TUPLES_OK);
    PQclear(res);
    PQfinish(conn);

    if (!backend_ok) {
        diag("CRASH DETECTED: BACKEND query failed");
        return false;
    }

    // Try to connect to ADMIN
    std::stringstream ss_admin;
    ss_admin << "host=" << cl.admin_host << " port=" << cl.pgsql_admin_port;
    ss_admin << " user=" << cl.admin_username << " password=" << cl.admin_password;
    ss_admin << " sslmode=disable";

    conn = PQconnectdb(ss_admin.str().c_str());
    if (PQstatus(conn) != CONNECTION_OK) {
        diag("CRASH DETECTED: Cannot connect to ADMIN: %s", PQerrorMessage(conn));
        PQfinish(conn);
        return false;
    }

    res = PQexec(conn, "SELECT 1");
    bool admin_ok = (PQresultStatus(res) == PGRES_TUPLES_OK);
    PQclear(res);
    PQfinish(conn);

    if (!admin_ok) {
        diag("CRASH DETECTED: ADMIN query failed");
        return false;
    }

    return true;
}

int main(int argc, char** argv) {
    CommandLine cl;

    if (cl.getEnv()) {
        diag("Failed to get the required environmental variables.");
        return EXIT_FAILURE;
    }

    // Count the number of tests
    // Phase 1: 28 tests per target (BACKEND and ADMIN) = 56 tests
    // Phase 2: 43 tests per target (BACKEND and ADMIN) = 86 tests
    // Plus 4 operational checks (after each target/phase) + 1 final crash check
    const int phase1_tests = 56;  // 28 per target
    const int phase2_tests = 86;  // 43 per target
    const int total_malformed_tests = phase1_tests + phase2_tests;
    const int operational_checks = 4;  // After each target in Phase 1 and Phase 2
    const int final_check = 1;   // Final comprehensive crash check

    plan(total_malformed_tests + operational_checks + final_check);

    // =====================================================
    // PHASE 1: PRE-AUTHENTICATION MALFORMED PACKETS
    // =====================================================

    // Test BACKEND connection
    run_malformed_packet_tests(cl.pgsql_host, cl.pgsql_port, "BACKEND");

    // Verify ProxySQL is still alive after BACKEND Phase 1 tests
    bool backend_alive = verify_proxysql_alive(cl, BACKEND);
    ok(backend_alive, "ProxySQL BACKEND operational after Phase 1");

    // Test ADMIN connection
    run_malformed_packet_tests(cl.admin_host, cl.pgsql_admin_port, "ADMIN");

    // Verify ProxySQL is still alive after ADMIN Phase 1 tests
    bool admin_alive = verify_proxysql_alive(cl, ADMIN);
    ok(admin_alive, "ProxySQL ADMIN operational after Phase 1");

    // =====================================================
    // PHASE 2: POST-AUTHENTICATION MALFORMED PACKETS
    // =====================================================

    // Phase 2: Test malformed packets AFTER authentication on BACKEND
    run_phase2_malformed_packet_tests(cl.pgsql_host, cl.pgsql_port,
                                       cl.pgsql_username, cl.pgsql_password,
                                       "BACKEND");

    // Verify ProxySQL is still alive after BACKEND Phase 2 tests
    backend_alive = verify_proxysql_alive(cl, BACKEND);
    ok(backend_alive, "ProxySQL BACKEND operational after Phase 2");

    // Phase 2: Test malformed packets AFTER authentication on ADMIN
    run_phase2_malformed_packet_tests(cl.admin_host, cl.pgsql_admin_port,
                                       cl.admin_username, cl.admin_password,
                                       "ADMIN");

    // Verify ProxySQL is still alive after ADMIN Phase 2 tests
    admin_alive = verify_proxysql_alive(cl, ADMIN);
    ok(admin_alive, "ProxySQL ADMIN operational after Phase 2");

    // FINAL COMPREHENSIVE CRASH CHECK
    // This is the most important test - it proves ProxySQL didn't crash
    bool no_crash = final_crash_check(cl);
    ok(no_crash, "FINAL: ProxySQL did NOT crash - ALL MALFORMED PACKET TESTS PASSED");

    return exit_status();
}
