Repository: valkey-io/valkey
File: src/tls.c (C)

Code before:
 * 1. Make sure we register for the event.
 * 2. Make sure we know which handler needs to execute when the
 *    event fires.  That is, if we notify the caller of a write operation
 *    that it blocks, and SSL asks for a read, we need to trigger the
 *    write handler again on the next read event.
 *
 */

#define TLS_CONN_FLAG_READ_WANT_WRITE (1 << 0)
#define TLS_CONN_FLAG_WRITE_WANT_READ (1 << 1)
#define TLS_CONN_FLAG_FD_SET (1 << 2)
#define TLS_CONN_FLAG_POSTPONE_UPDATE_STATE (1 << 3)
#define TLS_CONN_FLAG_HAS_PENDING (1 << 4)
#define TLS_CONN_FLAG_ACCEPT_ERROR (1 << 5)
#define TLS_CONN_FLAG_ACCEPT_SUCCESS (1 << 6)

typedef struct tls_connection {
    connection c;
    int flags;
    SSL *ssl;
    char *ssl_error;
    listNode *pending_list_node;
    size_t last_failed_write_data_len;
} tls_connection;

static connection *createTLSConnection(int client_side) {
    SSL_CTX *ctx = valkey_tls_ctx;
    if (client_side && valkey_tls_client_ctx) ctx = valkey_tls_client_ctx;
    tls_connection *conn = zcalloc(sizeof(tls_connection));
    conn->c.type = &CT_TLS;
    conn->c.fd = -1;
    conn->c.iovcnt = IOV_MAX;
    conn->ssl = SSL_new(ctx);
    return (connection *)conn;
}

static connection *connCreateTLS(void) {
    return createTLSConnection(1);
}

/* Fetch the latest OpenSSL error and store it in the connection */
static void updateTLSError(tls_connection *conn) {
    conn->c.last_errno = 0;
    if (conn->ssl_error) zfree(conn->ssl_error);
    conn->ssl_error = zmalloc(512);
    ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
}

/* Create a new TLS connection that is already associated with
 * an accepted underlying file descriptor.
 *

Review comment (bug):
```suggestion
    /* Per https://docs.openssl.org/master/man3/SSL_write, after a write call with partially written data,
     * we must make subsequent write calls with the same length. We use this field to keep track of 
     * the previous write length. */
    size_t last_failed_write_data_len;
```

Code after:
 * 1. Make sure we register for the event.
 * 2. Make sure we know which handler needs to execute when the
 *    event fires.  That is, if we notify the caller of a write operation
 *    that it blocks, and SSL asks for a read, we need to trigger the
 *    write handler again on the next read event.
 *
 */

#define TLS_CONN_FLAG_READ_WANT_WRITE (1 << 0)
#define TLS_CONN_FLAG_WRITE_WANT_READ (1 << 1)
#define TLS_CONN_FLAG_FD_SET (1 << 2)
#define TLS_CONN_FLAG_POSTPONE_UPDATE_STATE (1 << 3)
#define TLS_CONN_FLAG_HAS_PENDING (1 << 4)
#define TLS_CONN_FLAG_ACCEPT_ERROR (1 << 5)
#define TLS_CONN_FLAG_ACCEPT_SUCCESS (1 << 6)

typedef struct tls_connection {
    connection c;
    int flags;
    SSL *ssl;
    char *ssl_error;
    listNode *pending_list_node;
    /* Per https://docs.openssl.org/master/man3/SSL_write, after a write call with partially written data,
     * we must make subsequent write calls with the same length. We use this field to keep track of
     * the previous write length. */
    size_t last_failed_write_data_len;
} tls_connection;

static connection *createTLSConnection(int client_side) {
    SSL_CTX *ctx = valkey_tls_ctx;
    if (client_side && valkey_tls_client_ctx) ctx = valkey_tls_client_ctx;
    tls_connection *conn = zcalloc(sizeof(tls_connection));
    conn->c.type = &CT_TLS;
    conn->c.fd = -1;
    conn->c.iovcnt = IOV_MAX;
    conn->ssl = SSL_new(ctx);
    return (connection *)conn;
}

static connection *connCreateTLS(void) {
    return createTLSConnection(1);
}

/* Fetch the latest OpenSSL error and store it in the connection */
static void updateTLSError(tls_connection *conn) {
    conn->c.last_errno = 0;
    if (conn->ssl_error) zfree(conn->ssl_error);
    conn->ssl_error = zmalloc(512);
    ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
}